Browse Source

interim bugfix state

master
silberengel 8 months ago
parent
commit
5e72e31fca
  1. 170
      package-lock.json
  2. 57
      src/lib/components/CommentBox.svelte
  3. 40
      src/lib/components/EventDetails.svelte
  4. 5
      src/lib/components/EventInput.svelte
  5. 114
      src/lib/components/EventSearch.svelte
  6. 78
      src/lib/components/Login.svelte
  7. 28
      src/lib/components/LoginMenu.svelte
  8. 59
      src/lib/components/NetworkStatus.svelte
  9. 8
      src/lib/components/RelayActions.svelte
  10. 11
      src/lib/components/RelayDisplay.svelte
  11. 23
      src/lib/components/RelayStatus.svelte
  12. 199
      src/lib/components/publications/PublicationFeed.svelte
  13. 11
      src/lib/components/publications/PublicationHeader.svelte
  14. 10
      src/lib/components/publications/PublicationSection.svelte
  15. 14
      src/lib/components/util/CardActions.svelte
  16. 8
      src/lib/components/util/ContainingIndexes.svelte
  17. 45
      src/lib/components/util/Profile.svelte
  18. 5
      src/lib/components/util/ViewPublicationLink.svelte
  19. 55
      src/lib/consts.ts
  20. 7
      src/lib/navigator/EventNetwork/utils/networkBuilder.ts
  21. 405
      src/lib/ndk.ts
  22. 10
      src/lib/stores.ts
  23. 55
      src/lib/stores/networkStore.ts
  24. 4
      src/lib/stores/relayStore.ts
  25. 6
      src/lib/stores/userStore.ts
  26. 2
      src/lib/utils/ZettelParser.ts
  27. 22
      src/lib/utils/community_checker.ts
  28. 189
      src/lib/utils/network_detection.ts
  29. 104
      src/lib/utils/nostrEventService.ts
  30. 83
      src/lib/utils/nostrUtils.ts
  31. 4
      src/lib/utils/profile_search.ts
  32. 8
      src/lib/utils/relayDiagnostics.ts
  33. 424
      src/lib/utils/relay_management.ts
  34. 58
      src/lib/utils/subscription_search.ts
  35. 4
      src/routes/+layout.svelte
  36. 9
      src/routes/+layout.ts
  37. 22
      src/routes/+page.svelte
  38. 17
      src/routes/contact/+page.svelte
  39. 30
      src/routes/events/+page.svelte
  40. 5
      src/routes/publication/+page.ts

170
package-lock.json generated

@ -2559,18 +2559,37 @@ @@ -2559,18 +2559,37 @@
}
},
"node_modules/chokidar": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
"devOptional": true,
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
"dependencies": {
"readdirp": "^4.0.1"
"anymatch": "~3.1.2",
"braces": "~3.0.2",
"glob-parent": "~5.1.2",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
"normalize-path": "~3.0.0",
"readdirp": "~3.6.0"
},
"engines": {
"node": ">= 14.16.0"
"node": ">= 8.10.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
}
},
"node_modules/chokidar/node_modules/glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dependencies": {
"is-glob": "^4.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/cliui": {
@ -3431,6 +3450,15 @@ @@ -3431,6 +3450,15 @@
"node": ">=4"
}
},
"node_modules/eslint-plugin-svelte/node_modules/yaml": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
"dev": true,
"engines": {
"node": ">= 6"
}
},
"node_modules/eslint-scope": {
"version": "8.4.0",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
@ -5488,16 +5516,25 @@ @@ -5488,16 +5516,25 @@
}
},
"node_modules/readdirp": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
"devOptional": true,
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"dependencies": {
"picomatch": "^2.2.1"
},
"engines": {
"node": ">= 14.18.0"
"node": ">=8.10.0"
}
},
"node_modules/readdirp/node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"engines": {
"node": ">=8.6"
},
"funding": {
"type": "individual",
"url": "https://paulmillr.com/funding/"
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/require-directory": {
@ -5966,6 +6003,34 @@ @@ -5966,6 +6003,34 @@
"typescript": ">=5.0.0"
}
},
"node_modules/svelte-check/node_modules/chokidar": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
"dev": true,
"dependencies": {
"readdirp": "^4.0.1"
},
"engines": {
"node": ">= 14.16.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/svelte-check/node_modules/readdirp": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
"dev": true,
"engines": {
"node": ">= 14.18.0"
},
"funding": {
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/svelte-eslint-parser": {
"version": "0.43.0",
"resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-0.43.0.tgz",
@ -6184,51 +6249,6 @@ @@ -6184,51 +6249,6 @@
"node": ">=14.0.0"
}
},
"node_modules/tailwindcss/node_modules/chokidar": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
"dependencies": {
"anymatch": "~3.1.2",
"braces": "~3.0.2",
"glob-parent": "~5.1.2",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
"normalize-path": "~3.0.0",
"readdirp": "~3.6.0"
},
"engines": {
"node": ">= 8.10.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
}
},
"node_modules/tailwindcss/node_modules/chokidar/node_modules/glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dependencies": {
"is-glob": "^4.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/tailwindcss/node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/tailwindcss/node_modules/postcss-load-config": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz",
@ -6275,28 +6295,6 @@ @@ -6275,28 +6295,6 @@
"node": ">=4"
}
},
"node_modules/tailwindcss/node_modules/readdirp": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"dependencies": {
"picomatch": "^2.2.1"
},
"engines": {
"node": ">=8.10.0"
}
},
"node_modules/tailwindcss/node_modules/yaml": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz",
"integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==",
"bin": {
"yaml": "bin.mjs"
},
"engines": {
"node": ">= 14.6"
}
},
"node_modules/thenify": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
@ -6837,12 +6835,14 @@ @@ -6837,12 +6835,14 @@
}
},
"node_modules/yaml": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
"dev": true,
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz",
"integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==",
"bin": {
"yaml": "bin.mjs"
},
"engines": {
"node": ">= 6"
"node": ">= 14.6"
}
},
"node_modules/yargs": {

57
src/lib/components/CommentBox.svelte

@ -21,6 +21,7 @@ @@ -21,6 +21,7 @@
} from "$lib/utils/nostrEventService";
import { tick } from "svelte";
import { goto } from "$app/navigation";
import { activeInboxRelays, activeOutboxRelays } from "$lib/ndk";
const props = $props<{
event: NDKEvent;
@ -33,7 +34,7 @@ @@ -33,7 +34,7 @@
let success = $state<{ relay: string; eventId: string } | null>(null);
let error = $state<string | null>(null);
let showOtherRelays = $state(false);
let showFallbackRelays = $state(false);
let showSecondaryRelays = $state(false);
let userProfile = $state<NostrProfile | null>(null);
// Add state for modals and search
@ -150,7 +151,7 @@ @@ -150,7 +151,7 @@
preview = "";
error = null;
showOtherRelays = false;
showFallbackRelays = false;
showSecondaryRelays = false;
}
function removeFormatting() {
@ -169,7 +170,7 @@ @@ -169,7 +170,7 @@
async function handleSubmit(
useOtherRelays = false,
useFallbackRelays = false,
useSecondaryRelays = false,
) {
isSubmitting = true;
error = null;
@ -208,34 +209,30 @@ @@ -208,34 +209,30 @@
tags,
);
// Publish the event
const result = await publishEvent(
signedEvent,
useOtherRelays,
useFallbackRelays,
props.userRelayPreference,
);
// Publish the event using the new relay system
let relays = $activeOutboxRelays;
if (result.success) {
success = { relay: result.relay!, eventId: result.eventId! };
// Navigate to the published event
navigateToEvent(result.eventId!);
} else {
if (!useOtherRelays && !useFallbackRelays) {
showOtherRelays = true;
error =
"Failed to publish to primary relays. Would you like to try the other relays?";
} else if (useOtherRelays && !useFallbackRelays) {
showFallbackRelays = true;
error =
"Failed to publish to other relays. Would you like to try the fallback relays?";
} else {
error = "Failed to publish comment. Please try again later.";
if (useOtherRelays && !useSecondaryRelays) {
relays = [...$activeOutboxRelays, ...$activeInboxRelays];
} else if (useSecondaryRelays) {
// For secondary relays, use a subset of outbox relays
relays = $activeOutboxRelays.slice(0, 3); // Use first 3 outbox relays
}
}
} catch (e: unknown) {
console.error("Error publishing comment:", e);
error = e instanceof Error ? e.message : "An unexpected error occurred";
const successfulRelays = await publishEvent(signedEvent, relays);
success = {
relay: successfulRelays[0] || "Unknown relay",
eventId: signedEvent.id,
};
// Clear form after successful submission
content = "";
preview = "";
showOtherRelays = false;
showSecondaryRelays = false;
} catch (e) {
error = e instanceof Error ? e.message : "Unknown error occurred";
} finally {
isSubmitting = false;
}
@ -563,7 +560,7 @@ @@ -563,7 +560,7 @@
>Try Other Relays</Button
>
{/if}
{#if showFallbackRelays}
{#if showSecondaryRelays}
<Button
size="xs"
class="mt-2"

40
src/lib/components/EventDetails.svelte

@ -4,7 +4,7 @@ @@ -4,7 +4,7 @@
import { userBadge } from "$lib/snippets/UserSnippets.svelte";
import { toNpub } from "$lib/utils/nostrUtils";
import { neventEncode, naddrEncode, nprofileEncode } from "$lib/utils";
import { standardRelays } from "$lib/consts";
import { activeInboxRelays, activeOutboxRelays } from "$lib/ndk";
import type { NDKEvent } from "$lib/utils/nostrUtils";
import { getMatchingTags } from "$lib/utils/nostrUtils";
import ProfileHeader from "$components/cards/ProfileHeader.svelte";
@ -104,7 +104,7 @@ @@ -104,7 +104,7 @@
id: "",
sig: "",
} as any;
const naddr = naddrEncode(mockEvent, standardRelays);
const naddr = naddrEncode(mockEvent, $activeInboxRelays);
return `<a href='/events?id=${naddr}' class='underline text-primary-700'>a:${tag[1]}</a>`;
} catch (error) {
console.warn(
@ -134,7 +134,7 @@ @@ -134,7 +134,7 @@
pubkey: "",
sig: "",
} as any;
const nevent = neventEncode(mockEvent, standardRelays);
const nevent = neventEncode(mockEvent, $activeInboxRelays);
return `<a href='/events?id=${nevent}' class='underline text-primary-700'>e:${tag[1]}</a>`;
} catch (error) {
console.warn(
@ -160,7 +160,7 @@ @@ -160,7 +160,7 @@
pubkey: "",
sig: "",
} as any;
const nevent = neventEncode(mockEvent, standardRelays);
const nevent = neventEncode(mockEvent, $activeInboxRelays);
return `<a href='/events?id=${nevent}' class='underline text-primary-700'>note:${tag[1]}</a>`;
} catch (error) {
console.warn(
@ -201,7 +201,7 @@ @@ -201,7 +201,7 @@
id: "",
sig: "",
} as any;
const naddr = naddrEncode(mockEvent, standardRelays);
const naddr = naddrEncode(mockEvent, $activeInboxRelays);
return {
text: `a:${tag[1]}`,
gotoValue: naddr,
@ -230,7 +230,7 @@ @@ -230,7 +230,7 @@
pubkey: "",
sig: "",
} as any;
const nevent = neventEncode(mockEvent, standardRelays);
const nevent = neventEncode(mockEvent, $activeInboxRelays);
return {
text: `e:${tag[1]}`,
gotoValue: nevent,
@ -261,7 +261,7 @@ @@ -261,7 +261,7 @@
pubkey: "",
sig: "",
} as any;
const nevent = neventEncode(mockEvent, standardRelays);
const nevent = neventEncode(mockEvent, $activeInboxRelays);
return {
text: `note:${tag[1]}`,
gotoValue: nevent,
@ -290,6 +290,18 @@ @@ -290,6 +290,18 @@
return { text: `${tag[0]}:${tag[1]}` };
}
function getNeventUrl(event: NDKEvent): string {
return neventEncode(event, $activeInboxRelays);
}
function getNaddrUrl(event: NDKEvent): string {
return naddrEncode(event, $activeInboxRelays);
}
function getNprofileUrl(pubkey: string): string {
return nprofileEncode(pubkey, $activeInboxRelays);
}
$effect(() => {
if (event && event.kind !== 0 && event.content) {
parseBasicmarkup(event.content).then((html) => {
@ -329,14 +341,14 @@ @@ -329,14 +341,14 @@
// nprofile
ids.push({
label: "nprofile",
value: nprofileEncode(event.pubkey, standardRelays),
link: `/events?id=${nprofileEncode(event.pubkey, standardRelays)}`,
value: nprofileEncode(event.pubkey, $activeInboxRelays),
link: `/events?id=${nprofileEncode(event.pubkey, $activeInboxRelays)}`,
});
// nevent
ids.push({
label: "nevent",
value: neventEncode(event, standardRelays),
link: `/events?id=${neventEncode(event, standardRelays)}`,
value: neventEncode(event, $activeInboxRelays),
link: `/events?id=${neventEncode(event, $activeInboxRelays)}`,
});
// hex pubkey
ids.push({ label: "pubkey", value: event.pubkey });
@ -344,12 +356,12 @@ @@ -344,12 +356,12 @@
// nevent
ids.push({
label: "nevent",
value: neventEncode(event, standardRelays),
link: `/events?id=${neventEncode(event, standardRelays)}`,
value: neventEncode(event, $activeInboxRelays),
link: `/events?id=${neventEncode(event, $activeInboxRelays)}`,
});
// naddr (if addressable)
try {
const naddr = naddrEncode(event, standardRelays);
const naddr = naddrEncode(event, $activeInboxRelays);
ids.push({ label: "naddr", value: naddr, link: `/events?id=${naddr}` });
} catch {}
// hex id

5
src/lib/components/EventInput.svelte

@ -19,7 +19,7 @@ @@ -19,7 +19,7 @@
import { NDKEvent as NDKEventClass } from "@nostr-dev-kit/ndk";
import type { NDKEvent } from "$lib/utils/nostrUtils";
import { prefixNostrAddresses } from "$lib/utils/nostrUtils";
import { standardRelays } from "$lib/consts";
import { activeInboxRelays, activeOutboxRelays } from "$lib/ndk";
import { Button } from "flowbite-svelte";
import { nip19 } from "nostr-tools";
import { goto } from "$app/navigation";
@ -292,7 +292,8 @@ @@ -292,7 +292,8 @@
"wss://relay.damus.io",
"wss://relay.nostr.band",
"wss://nos.lol",
...standardRelays,
...$activeOutboxRelays,
...$activeInboxRelays,
];
let published = false;

114
src/lib/components/EventSearch.svelte

@ -1,17 +1,18 @@ @@ -1,17 +1,18 @@
<script lang="ts">
import { onMount } from 'svelte';
import { goto } from '$app/navigation';
import { Input, Button } from "flowbite-svelte";
import { Spinner } from "flowbite-svelte";
import { goto } from "$app/navigation";
import type { NDKEvent } from "$lib/utils/nostrUtils";
import RelayDisplay from "./RelayDisplay.svelte";
import {
searchEvent,
searchBySubscription,
searchNip05,
} from "$lib/utils/search_utility";
import { neventEncode, naddrEncode, nprofileEncode } from "$lib/utils";
import { standardRelays } from "$lib/consts";
import { activeInboxRelays, activeOutboxRelays } from "$lib/ndk";
import { getMatchingTags, toNpub } from "$lib/utils/nostrUtils";
import type { SearchResult } from '$lib/utils/search_types';
// Props definition
let {
@ -47,9 +48,6 @@ @@ -47,9 +48,6 @@
// Component state
let searchQuery = $state("");
let localError = $state<string | null>(null);
let relayStatuses = $state<Record<string, "pending" | "found" | "notfound">>(
{},
);
let foundEvent = $state<NDKEvent | null>(null);
let searching = $state(false);
let searchCompleted = $state(false);
@ -62,11 +60,7 @@ @@ -62,11 +60,7 @@
let currentAbortController: AbortController | null = null;
// Derived values
let hasActiveSearch = $derived(
searching ||
(Object.values(relayStatuses).some((s) => s === "pending") &&
!foundEvent),
);
let hasActiveSearch = $derived(searching && !foundEvent);
let showError = $derived(localError || error);
let showSuccess = $derived(searchCompleted && searchResultCount !== null);
@ -87,7 +81,7 @@ @@ -87,7 +81,7 @@
handleFoundEvent(foundEvent);
updateSearchState(false, true, 1, "nip05");
} else {
relayStatuses = {};
// relayStatuses = {}; // This line was removed as per the edit hint
if (activeSub) {
try {
activeSub.stop();
@ -105,7 +99,7 @@ @@ -105,7 +99,7 @@
} catch (error) {
localError =
error instanceof Error ? error.message : "NIP-05 lookup failed";
relayStatuses = {};
// relayStatuses = {}; // This line was removed as per the edit hint
if (activeSub) {
try {
activeSub.stop();
@ -132,7 +126,7 @@ @@ -132,7 +126,7 @@
if (!foundEvent) {
console.warn("[Events] Event not found for query:", query);
localError = "Event not found";
relayStatuses = {};
// relayStatuses = {}; // This line was removed as per the edit hint
if (activeSub) {
try {
activeSub.stop();
@ -154,7 +148,7 @@ @@ -154,7 +148,7 @@
} catch (err) {
console.error("[Events] Error fetching event:", err, "Query:", query);
localError = "Error fetching event. Please check the ID and try again.";
relayStatuses = {};
// relayStatuses = {}; // This line was removed as per the edit hint
if (activeSub) {
try {
activeSub.stop();
@ -186,7 +180,7 @@ @@ -186,7 +180,7 @@
isResetting = false;
isUserEditing = false; // Reset user editing flag when search starts
const query = (
queryOverride !== undefined ? queryOverride : searchQuery
queryOverride !== undefined ? queryOverride || "" : searchQuery || ""
).trim();
if (!query) {
updateSearchState(false, false, null, null);
@ -264,11 +258,11 @@ @@ -264,11 +258,11 @@
let currentNevent = null;
let currentNpub = null;
try {
currentNevent = neventEncode(foundEvent, standardRelays);
currentNevent = neventEncode(foundEvent, $activeInboxRelays);
} catch {}
try {
currentNaddr = getMatchingTags(foundEvent, "d")[0]?.[1]
? naddrEncode(foundEvent, standardRelays)
? naddrEncode(foundEvent, $activeInboxRelays)
: null;
} catch {}
try {
@ -301,7 +295,7 @@ @@ -301,7 +295,7 @@
foundEvent.kind === 0
) {
try {
currentNprofile = nprofileEncode(foundEvent.pubkey, standardRelays);
currentNprofile = nprofileEncode(foundEvent.pubkey, $activeInboxRelays);
} catch {}
}
@ -324,7 +318,9 @@ @@ -324,7 +318,9 @@
searchTimeout = setTimeout(() => {
isProcessingSearch = true;
isWaitingForSearchResult = true;
if (searchValue) {
handleSearchEvent(false, searchValue);
}
}, 300);
});
@ -350,8 +346,14 @@ @@ -350,8 +346,14 @@
) {
console.log("EventSearch: Processing dTagValue:", dTagValue);
lastProcessedDTagValue = dTagValue;
// Add a small delay to prevent rapid successive calls
setTimeout(() => {
if (!searching && !isResetting) {
handleSearchBySubscription("d", dTagValue);
}
}, 100);
}
});
// Simple effect to handle event prop changes
@ -380,7 +382,6 @@ @@ -380,7 +382,6 @@
function resetSearchState() {
isResetting = true;
foundEvent = null;
relayStatuses = {};
localError = null;
lastProcessedSearchValue = null;
lastProcessedDTagValue = null;
@ -422,7 +423,7 @@ @@ -422,7 +423,7 @@
function handleFoundEvent(event: NDKEvent) {
foundEvent = event;
relayStatuses = {}; // Clear relay statuses when event is found
localError = null; // Clear local error when event is found
// Stop any ongoing subscription
if (activeSub) {
@ -481,13 +482,42 @@ @@ -481,13 +482,42 @@
isResetting = false; // Allow effects to run for new searches
localError = null;
updateSearchState(true);
// Wait for relays to be available (with timeout)
let retryCount = 0;
const maxRetries = 10; // Wait up to 5 seconds (10 * 500ms)
while ($activeInboxRelays.length === 0 && $activeOutboxRelays.length === 0 && retryCount < maxRetries) {
console.debug(`EventSearch: Waiting for relays... (attempt ${retryCount + 1}/${maxRetries})`);
await new Promise(resolve => setTimeout(resolve, 500)); // Wait 500ms
retryCount++;
}
// Check if we have any relays available
if ($activeInboxRelays.length === 0 && $activeOutboxRelays.length === 0) {
console.warn("EventSearch: No relays available after waiting, failing search");
localError = "No relays available. Please check your connection and try again.";
updateSearchState(false, false, null, null);
isProcessingSearch = false;
currentProcessingSearchValue = null;
isWaitingForSearchResult = false;
searching = false;
return;
}
console.log("EventSearch: Relays available, proceeding with search:", {
inboxCount: $activeInboxRelays.length,
outboxCount: $activeOutboxRelays.length
});
try {
// Cancel existing search
if (currentAbortController) {
currentAbortController.abort();
}
currentAbortController = new AbortController();
const result = await searchBySubscription(
// Add a timeout to prevent hanging searches
const searchPromise = searchBySubscription(
searchType,
searchTerm,
{
@ -513,6 +543,15 @@ @@ -513,6 +543,15 @@
},
currentAbortController.signal,
);
// Add a 30-second timeout
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => {
reject(new Error("Search timeout: No results received within 30 seconds"));
}, 30000);
});
const result = await Promise.race([searchPromise, timeoutPromise]) as any;
console.log("EventSearch: Search completed:", result);
onSearchResults(
result.events,
@ -527,7 +566,7 @@ @@ -527,7 +566,7 @@
result.events.length +
result.secondOrder.length +
result.tTagEvents.length;
relayStatuses = {}; // Clear relay statuses when search completes
localError = null; // Clear local error when search completes
// Stop any ongoing subscription
if (activeSub) {
try {
@ -570,7 +609,7 @@ @@ -570,7 +609,7 @@
localError = `Search failed: ${error.message}`;
}
}
relayStatuses = {}; // Clear relay statuses when search fails
localError = null; // Clear local error when search fails
// Stop any ongoing subscription
if (activeSub) {
try {
@ -611,7 +650,6 @@ @@ -611,7 +650,6 @@
searchResultCount = null;
searchResultType = null;
foundEvent = null;
relayStatuses = {};
localError = null;
isProcessingSearch = false;
currentProcessingSearchValue = null;
@ -651,6 +689,18 @@ @@ -651,6 +689,18 @@
? `Search completed. Found 1 ${typeLabel}.`
: `Search completed. Found ${searchResultCount} ${countLabel}.`;
}
function getNeventUrl(event: NDKEvent): string {
return neventEncode(event, $activeInboxRelays);
}
function getNaddrUrl(event: NDKEvent): string {
return naddrEncode(event, $activeInboxRelays);
}
function getNprofileUrl(pubkey: string): string {
return nprofileEncode(pubkey, $activeInboxRelays);
}
</script>
<div class="flex flex-col space-y-6">
@ -700,18 +750,4 @@ @@ -700,18 +750,4 @@
{getResultMessage()}
</div>
{/if}
<!-- Relay Status Display -->
<div class="mt-4">
<div class="flex flex-wrap gap-2">
{#each Object.entries(relayStatuses) as [relay, status]}
<RelayDisplay {relay} showStatus={true} {status} />
{/each}
</div>
{#if !foundEvent && hasActiveSearch}
<div class="text-gray-700 dark:text-gray-300 mt-2">
Searching relays...
</div>
{/if}
</div>
</div>

78
src/lib/components/Login.svelte

@ -1,78 +0,0 @@ @@ -1,78 +0,0 @@
<script lang="ts">
import { type NDKUserProfile } from "@nostr-dev-kit/ndk";
import {
activePubkey,
loginWithExtension,
ndkInstance,
ndkSignedIn,
persistLogin,
} from "$lib/ndk";
import { Avatar, Button, Popover } from "flowbite-svelte";
import Profile from "$components/util/Profile.svelte";
let profile = $state<NDKUserProfile | null>(null);
let npub = $state<string | undefined>(undefined);
let signInFailed = $state<boolean>(false);
let errorMessage = $state<string>("");
$effect(() => {
if ($ndkSignedIn) {
$ndkInstance
.getUser({ pubkey: $activePubkey ?? undefined })
?.fetchProfile()
.then((userProfile) => {
profile = userProfile;
});
npub = $ndkInstance.activeUser?.npub;
}
});
async function handleSignInClick() {
try {
signInFailed = false;
errorMessage = "";
const user = await loginWithExtension();
if (!user) {
throw new Error("The NIP-07 extension did not return a user.");
}
profile = await user.fetchProfile();
persistLogin(user);
} catch (e) {
console.error(e);
signInFailed = true;
errorMessage =
e instanceof Error ? e.message : "Failed to sign in. Please try again.";
}
}
</script>
<div class="m-4">
{#if $ndkSignedIn}
<Profile pubkey={$activePubkey} isNav={true} />
{:else}
<Avatar rounded class="h-6 w-6 cursor-pointer bg-transparent" id="avatar" />
<Popover
class="popover-leather w-fit"
placement="bottom"
triggeredBy="#avatar"
>
<div class="w-full flex flex-col space-y-2">
<Button onclick={handleSignInClick}>Extension Sign-In</Button>
{#if signInFailed}
<div class="p-2 text-sm text-red-600 bg-red-100 rounded">
{errorMessage}
</div>
{/if}
<!-- <Button
color='alternative'
on:click={signInWithBunker}
>
Bunker Sign-In
</Button> -->
</div>
</Popover>
{/if}
</div>

28
src/lib/components/LoginMenu.svelte

@ -11,10 +11,11 @@ @@ -11,10 +11,11 @@
loginWithNpub,
logoutUser,
} from "$lib/stores/userStore";
import { get } from "svelte/store";
import NDK, { NDKNip46Signer, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";
import { onMount } from "svelte";
import { goto } from "$app/navigation";
import NetworkStatus from "./NetworkStatus.svelte";
// UI state
let isLoadingExtension: boolean = $state(false);
@ -36,13 +37,16 @@ @@ -36,13 +37,16 @@
}
});
// Subscribe to userStore
let user = $state(get(userStore));
userStore.subscribe((val) => {
user = val;
// Use reactive user state from store
let user = $derived($userStore);
// Handle user state changes with effects
$effect(() => {
const currentUser = user;
// Check for fallback flag when user state changes to signed in
if (
val.signedIn &&
currentUser.signedIn &&
localStorage.getItem("alexandria/amber/fallback") === "1" &&
!showAmberFallback
) {
@ -53,7 +57,7 @@ @@ -53,7 +57,7 @@
}
// Set up periodic check when user is signed in
if (val.signedIn && !fallbackCheckInterval) {
if (currentUser.signedIn && !fallbackCheckInterval) {
fallbackCheckInterval = setInterval(() => {
if (
localStorage.getItem("alexandria/amber/fallback") === "1" &&
@ -65,7 +69,7 @@ @@ -65,7 +69,7 @@
showAmberFallback = true;
}
}, 500); // Check every 500ms
} else if (!val.signedIn && fallbackCheckInterval) {
} else if (!currentUser.signedIn && fallbackCheckInterval) {
clearInterval(fallbackCheckInterval);
fallbackCheckInterval = null;
}
@ -249,6 +253,10 @@ @@ -249,6 +253,10 @@
>
📖 npub (read only)
</button>
<div class="border-t border-gray-200 pt-2 mt-2">
<div class="text-xs text-gray-500 mb-1">Network Status:</div>
<NetworkStatus />
</div>
</div>
</Popover>
{#if result}
@ -313,6 +321,10 @@ @@ -313,6 +321,10 @@
Unknown login method
{/if}
</li>
<li class="border-t border-gray-200 pt-2 mt-2">
<div class="text-xs text-gray-500 mb-1">Network Status:</div>
<NetworkStatus />
</li>
<li>
<button
id="sign-out-button"

59
src/lib/components/NetworkStatus.svelte

@ -0,0 +1,59 @@ @@ -0,0 +1,59 @@
<script lang="ts">
import { networkCondition, isNetworkChecking, startNetworkStatusMonitoring } from '$lib/stores/networkStore';
import { NetworkCondition } from '$lib/utils/network_detection';
import { onMount } from 'svelte';
function getStatusColor(): string {
switch ($networkCondition) {
case NetworkCondition.ONLINE:
return 'text-green-600 dark:text-green-400';
case NetworkCondition.SLOW:
return 'text-yellow-600 dark:text-yellow-400';
case NetworkCondition.OFFLINE:
return 'text-red-600 dark:text-red-400';
default:
return 'text-gray-600 dark:text-gray-400';
}
}
function getStatusIcon(): string {
switch ($networkCondition) {
case NetworkCondition.ONLINE:
return '🟢';
case NetworkCondition.SLOW:
return '🟡';
case NetworkCondition.OFFLINE:
return '🔴';
default:
return '⚪';
}
}
function getStatusText(): string {
switch ($networkCondition) {
case NetworkCondition.ONLINE:
return 'Online';
case NetworkCondition.SLOW:
return 'Slow Connection';
case NetworkCondition.OFFLINE:
return 'Offline';
default:
return 'Unknown';
}
}
onMount(() => {
// Start centralized network monitoring
startNetworkStatusMonitoring();
});
</script>
<div class="flex items-center space-x-2 text-xs {getStatusColor()} font-medium">
{#if $isNetworkChecking}
<span class="animate-spin"></span>
<span>Checking...</span>
{:else}
<span class="text-lg">{getStatusIcon()}</span>
<span>{getStatusText()}</span>
{/if}
</div>

8
src/lib/components/RelayActions.svelte

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
<script lang="ts">
import { Button, Modal } from "flowbite-svelte";
import { ndkInstance } from "$lib/ndk";
import { ndkInstance, activeInboxRelays, activeOutboxRelays } from "$lib/ndk";
import { get } from "svelte/store";
import type { NDKEvent } from "$lib/utils/nostrUtils";
import {
@ -11,7 +11,7 @@ @@ -11,7 +11,7 @@
getConnectedRelays,
getEventRelays,
} from "./RelayDisplay.svelte";
import { standardRelays, fallbackRelays } from "$lib/consts";
import { communityRelays, secondaryRelays } from "$lib/consts";
const { event } = $props<{
event: NDKEvent;
@ -43,7 +43,7 @@ @@ -43,7 +43,7 @@
const userRelays = Array.from(ndk?.pool?.relays.values() || []).map(
(r) => r.url,
);
allRelays = [...standardRelays, ...userRelays, ...fallbackRelays].filter(
allRelays = [...$activeInboxRelays, ...$activeOutboxRelays, ...userRelays].filter(
(url, idx, arr) => arr.indexOf(url) === idx,
);
relaySearchResults = Object.fromEntries(
@ -108,7 +108,7 @@ @@ -108,7 +108,7 @@
size="lg"
>
<div class="flex flex-col gap-4 max-h-96 overflow-y-auto">
{#each Object.entries( { "Standard Relays": standardRelays, "User Relays": Array.from($ndkInstance?.pool?.relays.values() || []).map((r) => r.url), "Fallback Relays": fallbackRelays }, ) as [groupName, groupRelays]}
{#each Object.entries( { "Active Inbox Relays": $activeInboxRelays, "Active Outbox Relays": $activeOutboxRelays }, ) as [groupName, groupRelays]}
{#if groupRelays.length > 0}
<div class="flex flex-col gap-2">
<h3

11
src/lib/components/RelayDisplay.svelte

@ -1,7 +1,9 @@ @@ -1,7 +1,9 @@
<script lang="ts" context="module">
import type { NDKEvent } from "$lib/utils/nostrUtils";
import { get } from "svelte/store";
import { activeInboxRelays, ndkInstance } from "$lib/ndk";
// Get relays from event (prefer event.relay or event.relays, fallback to standardRelays)
// Get relays from event (prefer event.relay or event.relays, fallback to active inbox relays)
export function getEventRelays(event: NDKEvent): string[] {
if (event && (event as any).relay) {
const relay = (event as any).relay;
@ -12,7 +14,8 @@ @@ -12,7 +14,8 @@
typeof r === "string" ? r : r.url,
);
}
return standardRelays;
// Use active inbox relays as fallback
return get(activeInboxRelays);
}
export function getConnectedRelays(): string[] {
@ -24,10 +27,6 @@ @@ -24,10 +27,6 @@
</script>
<script lang="ts">
import { get } from "svelte/store";
import { ndkInstance } from "$lib/ndk";
import { standardRelays } from "$lib/consts";
export let relay: string;
export let showStatus = false;
export let status: "pending" | "found" | "notfound" | null = null;

23
src/lib/components/RelayStatus.svelte

@ -7,11 +7,8 @@ @@ -7,11 +7,8 @@
checkWebSocketSupport,
checkEnvironmentForWebSocketDowngrade,
} from "$lib/ndk";
import { standardRelays, anonymousRelays } from "$lib/consts";
import { onMount } from "svelte";
import { feedType } from "$lib/stores";
import { inboxRelays, outboxRelays } from "$lib/ndk";
import { FeedType } from "$lib/consts";
import { activeInboxRelays, activeOutboxRelays } from "$lib/ndk";
interface RelayStatus {
url: string;
@ -24,6 +21,13 @@ @@ -24,6 +21,13 @@
let relayStatuses = $state<RelayStatus[]>([]);
let testing = $state(false);
// Use the new relay management system
let allRelays: string[] = $state([]);
$effect(() => {
allRelays = [...$activeInboxRelays, ...$activeOutboxRelays];
});
async function runRelayTests() {
testing = true;
const ndk = $ndkInstance;
@ -34,16 +38,9 @@ @@ -34,16 +38,9 @@
let relaysToTest: string[] = [];
if ($feedType === FeedType.UserRelays && $ndkSignedIn) {
// Use user's relays (inbox + outbox), deduplicated
const userRelays = new Set([...$inboxRelays, ...$outboxRelays]);
// Use active relays from the new relay management system
const userRelays = new Set([...$activeInboxRelays, ...$activeOutboxRelays]);
relaysToTest = Array.from(userRelays);
} else {
// Use default relays (standard + anonymous), deduplicated
relaysToTest = Array.from(
new Set([...standardRelays, ...anonymousRelays]),
);
}
console.log("[RelayStatus] Relays to test:", relaysToTest);

199
src/lib/components/publications/PublicationFeed.svelte

@ -1,10 +1,10 @@ @@ -1,10 +1,10 @@
<script lang="ts">
import { indexKind } from "$lib/consts";
import { ndkInstance } from "$lib/ndk";
import { ndkInstance, activeInboxRelays, activeOutboxRelays } from "$lib/ndk";
import { filterValidIndexEvents, debounce } from "$lib/utils";
import { Button, P, Skeleton, Spinner, Checkbox } from "flowbite-svelte";
import { Button, P, Skeleton, Spinner } from "flowbite-svelte";
import ArticleHeader from "./PublicationHeader.svelte";
import { onMount } from "svelte";
import { onMount, onDestroy } from "svelte";
import {
getMatchingTags,
NDKRelaySetFromNDK,
@ -15,44 +15,109 @@ @@ -15,44 +15,109 @@
import { indexEventCache } from "$lib/utils/indexEventCache";
import { isValidNip05Address } from "$lib/utils/search_utility";
let {
relays,
fallbackRelays,
searchQuery = "",
userRelays = [],
} = $props<{
relays: string[];
fallbackRelays: string[];
const props = $props<{
searchQuery?: string;
userRelays?: string[];
onEventCountUpdate?: (counts: { displayed: number; total: number }) => void;
}>();
// Component state
let eventsInView: NDKEvent[] = $state([]);
let loadingMore: boolean = $state(false);
let endOfFeed: boolean = $state(false);
let relayStatuses = $state<Record<string, "pending" | "found" | "notfound">>(
{},
);
let relayStatuses = $state<Record<string, "pending" | "found" | "notfound">>({});
let loading: boolean = $state(true);
let hasInitialized = $state(false);
let fallbackTimeout: ReturnType<typeof setTimeout> | null = null;
// Relay management
let allRelays: string[] = $state([]);
let ndk = $derived($ndkInstance);
// Event management
let allIndexEvents: NDKEvent[] = $state([]);
let cutoffTimestamp: number = $derived(
eventsInView?.at(eventsInView.length - 1)?.created_at ??
new Date().getTime(),
);
let allIndexEvents: NDKEvent[] = $state([]);
// Initialize relays and fetch events
async function initializeAndFetch() {
if (!ndk) {
console.debug('[PublicationFeed] No NDK instance available');
return;
}
// Get relays from active stores
const inboxRelays = $activeInboxRelays;
const outboxRelays = $activeOutboxRelays;
const newRelays = [...inboxRelays, ...outboxRelays];
console.debug('[PublicationFeed] Available relays:', {
inboxCount: inboxRelays.length,
outboxCount: outboxRelays.length,
totalCount: newRelays.length,
relays: newRelays
});
if (newRelays.length === 0) {
console.debug('[PublicationFeed] No relays available, waiting...');
return;
}
// Update allRelays if different
const currentRelaysString = allRelays.sort().join(',');
const newRelaysString = newRelays.sort().join(',');
if (currentRelaysString !== newRelaysString) {
allRelays = newRelays;
console.debug('[PublicationFeed] Relays updated, fetching events');
await fetchAllIndexEventsFromRelays();
}
}
// Watch for relay store changes
$effect(() => {
const inboxRelays = $activeInboxRelays;
const outboxRelays = $activeOutboxRelays;
const newRelays = [...inboxRelays, ...outboxRelays];
if (newRelays.length > 0 && !hasInitialized) {
console.debug('[PublicationFeed] Relays available, initializing');
hasInitialized = true;
if (fallbackTimeout) {
clearTimeout(fallbackTimeout);
fallbackTimeout = null;
}
setTimeout(() => initializeAndFetch(), 0);
} else if (newRelays.length === 0 && !hasInitialized) {
console.debug('[PublicationFeed] No relays available, setting up fallback');
if (!fallbackTimeout) {
fallbackTimeout = setTimeout(() => {
console.debug('[PublicationFeed] Fallback timeout reached, retrying');
hasInitialized = true;
initializeAndFetch();
}, 3000);
}
}
});
async function fetchAllIndexEventsFromRelays() {
loading = true;
const ndk = $ndkInstance;
const communityRelays: string[] = relays;
const userRelayList: string[] = userRelays || [];
const fallback: string[] = fallbackRelays.filter(
(r: string) => !communityRelays.includes(r) && !userRelayList.includes(r),
);
const allRelays = includeAllRelays
? [...communityRelays, ...userRelayList, ...fallback]
: [...communityRelays, ...userRelayList];
console.debug('[PublicationFeed] fetchAllIndexEventsFromRelays called with relays:', {
allRelaysCount: allRelays.length,
allRelays: allRelays
});
if (!ndk) {
console.error('[PublicationFeed] No NDK instance available');
loading = false;
return;
}
if (allRelays.length === 0) {
console.debug('[PublicationFeed] No relays available for fetching');
loading = false;
return;
}
// Check cache first
const cachedEvents = indexEventCache.get(allRelays);
@ -67,6 +132,7 @@ @@ -67,6 +132,7 @@
return;
}
loading = true;
relayStatuses = Object.fromEntries(
allRelays.map((r: string) => [r, "pending"]),
);
@ -75,11 +141,13 @@ @@ -75,11 +141,13 @@
// Helper to fetch from a single relay with timeout
async function fetchFromRelay(relay: string): Promise<NDKEvent[]> {
try {
console.debug(`[PublicationFeed] Fetching from relay: ${relay}`);
const relaySet = NDKRelaySetFromNDK.fromRelayUrls([relay], ndk);
let eventSet = await ndk
.fetchEvents(
{
kinds: [indexKind],
limit: 1000, // Increased limit to get more events
},
{
groupable: false,
@ -88,29 +156,40 @@ @@ -88,29 +156,40 @@
},
relaySet,
)
.withTimeout(5000);
.withTimeout(10000); // Increased timeout to 10 seconds
console.debug(`[PublicationFeed] Raw events from ${relay}:`, eventSet.size);
eventSet = filterValidIndexEvents(eventSet);
console.debug(`[PublicationFeed] Valid events from ${relay}:`, eventSet.size);
relayStatuses = { ...relayStatuses, [relay]: "found" };
return Array.from(eventSet);
} catch (err) {
console.error(`Error fetching from relay ${relay}:`, err);
console.error(`[PublicationFeed] Error fetching from relay ${relay}:`, err);
relayStatuses = { ...relayStatuses, [relay]: "notfound" };
return [];
}
}
// Fetch from all relays in parallel, do not block on any single relay
console.debug(`[PublicationFeed] Starting fetch from ${allRelays.length} relays`);
const results = await Promise.allSettled(allRelays.map(fetchFromRelay));
for (const result of results) {
if (result.status === "fulfilled") {
allEvents = allEvents.concat(result.value);
}
}
console.debug(`[PublicationFeed] Total events fetched:`, allEvents.length);
// Deduplicate by tagAddress
const eventMap = new Map(
allEvents.map((event) => [event.tagAddress(), event]),
);
allIndexEvents = Array.from(eventMap.values());
console.debug(`[PublicationFeed] Events after deduplication:`, allIndexEvents.length);
// Sort by created_at descending
allIndexEvents.sort((a, b) => b.created_at! - a.created_at!);
@ -121,12 +200,19 @@ @@ -121,12 +200,19 @@
eventsInView = allIndexEvents.slice(0, 30);
endOfFeed = allIndexEvents.length <= 30;
loading = false;
console.debug(`[PublicationFeed] Final state:`, {
totalEvents: allIndexEvents.length,
eventsInView: eventsInView.length,
endOfFeed,
loading
});
}
// Function to filter events based on search query
const filterEventsBySearch = (events: NDKEvent[]) => {
if (!searchQuery) return events;
const query = searchQuery.toLowerCase();
if (!props.searchQuery) return events;
const query = props.searchQuery.toLowerCase();
console.debug(
"[PublicationFeed] Filtering events with query:",
query,
@ -219,15 +305,25 @@ @@ -219,15 +305,25 @@
$effect(() => {
console.debug(
"[PublicationFeed] Search query effect triggered:",
searchQuery,
props.searchQuery,
);
debouncedSearch(searchQuery);
debouncedSearch(props.searchQuery);
});
// Emit event count updates
$effect(() => {
if (props.onEventCountUpdate) {
props.onEventCountUpdate({
displayed: eventsInView.length,
total: allIndexEvents.length
});
}
});
async function loadMorePublications() {
loadingMore = true;
const current = eventsInView.length;
let source = searchQuery.trim()
let source = props.searchQuery.trim()
? filterEventsBySearch(allIndexEvents)
: allIndexEvents;
eventsInView = source.slice(0, current + 30);
@ -251,38 +347,27 @@ @@ -251,38 +347,27 @@
return `Index: ${indexStats.size} entries (${indexStats.totalEvents} events), Search: ${searchStats} entries`;
}
// Include all relays checkbox state
let includeAllRelays = $state(false);
// Watch for changes in include all relays setting
$effect(() => {
console.log(
`[PublicationFeed] Include all relays setting changed to: ${includeAllRelays}`,
);
// Clear cache when relay configuration changes
indexEventCache.clear();
searchCache.clear();
// Cleanup function for fallback timeout
function cleanup() {
if (fallbackTimeout) {
clearTimeout(fallbackTimeout);
fallbackTimeout = null;
}
}
// Refetch events with new relay configuration
fetchAllIndexEventsFromRelays();
// Cleanup on component destruction
onDestroy(() => {
cleanup();
});
onMount(async () => {
await fetchAllIndexEventsFromRelays();
console.debug('[PublicationFeed] onMount called');
// The effect will handle fetching when relays become available
});
</script>
<div class="flex flex-col space-y-4">
<!-- Include all relays checkbox -->
<div class="flex items-center justify-center">
<Checkbox bind:checked={includeAllRelays} class="mr-2" />
<label
for="include-all-relays"
class="text-sm text-gray-700 dark:text-gray-300"
>
Include all relays (slower but more comprehensive search)
</label>
</div>
<div
class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 w-full"

11
src/lib/components/publications/PublicationHeader.svelte

@ -2,15 +2,20 @@ @@ -2,15 +2,20 @@
import { ndkInstance } from "$lib/ndk";
import { naddrEncode } from "$lib/utils";
import type { NDKEvent } from "@nostr-dev-kit/ndk";
import { standardRelays } from "../../consts";
import { activeInboxRelays, activeOutboxRelays } from "$lib/ndk";
import { communityRelays } from "../../consts";
import { Card, Img } from "flowbite-svelte";
import CardActions from "$components/util/CardActions.svelte";
import { userBadge } from "$lib/snippets/UserSnippets.svelte";
const { event } = $props<{ event: NDKEvent }>();
function getRelayUrls(): string[] {
return $activeInboxRelays;
}
const relays = $derived.by(() => {
return $ndkInstance.activeUser?.relayUrls ?? standardRelays;
return getRelayUrls();
});
const href = $derived.by(() => {
@ -33,8 +38,6 @@ @@ -33,8 +38,6 @@
let authorPubkey: string = $derived(
event.getMatchingTags("p")[0]?.[1] ?? null,
);
console.log("PublicationHeader event:", event);
</script>
{#if title != null && href != null}

10
src/lib/components/publications/PublicationSection.svelte

@ -10,6 +10,7 @@ @@ -10,6 +10,7 @@
import type { Asciidoctor, Document } from "asciidoctor";
import { getMatchingTags } from "$lib/utils/nostrUtils";
import type { SveltePublicationTree } from "./svelte_publication_tree.svelte";
import { postProcessAdvancedAsciidoctorHtml } from "$lib/utils/markup/advancedAsciidoctorPostProcessor";
let {
address,
@ -46,9 +47,12 @@ @@ -46,9 +47,12 @@
async () => (await leafEvent)?.getMatchingTags("title")[0]?.[1],
);
let leafContent: Promise<string | Document> = $derived.by(async () =>
asciidoctor.convert((await leafEvent)?.content ?? ""),
);
let leafContent: Promise<string | Document> = $derived.by(async () => {
const content = (await leafEvent)?.content ?? "";
const converted = asciidoctor.convert(content);
const processed = await postProcessAdvancedAsciidoctorHtml(converted.toString());
return processed;
});
let previousLeafEvent: NDKEvent | null = $derived.by(() => {
let index: number;

14
src/lib/components/util/CardActions.svelte

@ -8,9 +8,8 @@ @@ -8,9 +8,8 @@
import CopyToClipboard from "$components/util/CopyToClipboard.svelte";
import { userBadge } from "$lib/snippets/UserSnippets.svelte";
import { neventEncode, naddrEncode } from "$lib/utils";
import { standardRelays, fallbackRelays, FeedType } from "$lib/consts";
import { ndkSignedIn, inboxRelays } from "$lib/ndk";
import { feedType } from "$lib/stores";
import { communityRelays, secondaryRelays, FeedType } from "$lib/consts";
import { activeInboxRelays, activeOutboxRelays } from "$lib/ndk";
import { userStore } from "$lib/stores/userStore";
import { goto } from "$app/navigation";
import type { NDKEvent } from "$lib/utils/nostrUtils";
@ -63,19 +62,16 @@ @@ -63,19 +62,16 @@
/**
* Selects the appropriate relay set based on user state and feed type
* - Uses user's inbox relays when signed in and viewing personal feed
* - Falls back to standard relays for anonymous users or standard feed
* - Uses active inbox relays from the new relay management system
* - Falls back to active inbox relays for anonymous users (which include community relays)
*/
let activeRelays = $derived(
(() => {
const isUserFeed = user.signedIn && $feedType === FeedType.UserRelays;
const relays = isUserFeed ? user.relays.inbox : standardRelays;
const relays = user.signedIn ? $activeInboxRelays : $activeInboxRelays;
console.debug("[CardActions] Selected relays:", {
eventId: event.id,
isSignedIn: user.signedIn,
feedType: $feedType,
isUserFeed,
relayCount: relays.length,
relayUrls: relays,
});

8
src/lib/components/util/ContainingIndexes.svelte

@ -5,7 +5,7 @@ @@ -5,7 +5,7 @@
import { findContainingIndexEvents } from "$lib/utils/event_search";
import { getMatchingTags } from "$lib/utils/nostrUtils";
import { naddrEncode } from "$lib/utils";
import { standardRelays } from "$lib/consts";
import { activeInboxRelays, activeOutboxRelays } from "$lib/ndk";
let { event } = $props<{
event: NDKEvent;
@ -51,7 +51,7 @@ @@ -51,7 +51,7 @@
} else {
// Fallback to naddr
try {
const naddr = naddrEncode(indexEvent, standardRelays);
const naddr = naddrEncode(indexEvent, $activeInboxRelays);
goto(`/publication?id=${encodeURIComponent(naddr)}`);
} catch (err) {
console.error("[ContainingIndexes] Error creating naddr:", err);
@ -59,6 +59,10 @@ @@ -59,6 +59,10 @@
}
}
function getNaddrUrl(event: NDKEvent): string {
return naddrEncode(event, $activeInboxRelays);
}
$effect(() => {
// Only reload if the event ID has actually changed
if (event.id !== lastEventId) {

45
src/lib/components/util/Profile.svelte

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
<script lang="ts">
import CopyToClipboard from "$components/util/CopyToClipboard.svelte";
import { logoutUser } from "$lib/stores/userStore";
import NetworkStatus from "$components/NetworkStatus.svelte";
import { logoutUser, userStore } from "$lib/stores/userStore";
import { ndkInstance } from "$lib/ndk";
import {
ArrowRightToBracketOutline,
@ -14,22 +15,29 @@ @@ -14,22 +15,29 @@
let { pubkey, isNav = false } = $props();
let profile = $state<NDKUserProfile | null>(null);
let pfp = $derived(profile?.image);
// Use profile data from userStore instead of fetching separately
let userState = $derived($userStore);
let profile = $derived(userState.profile);
let pfp = $derived(profile?.picture);
let username = $derived(profile?.name);
let tag = $derived(profile?.name);
let npub = $state<string | undefined>(undefined);
let npub = $derived(userState.npub);
// Fallback to fetching profile if not available in userStore
$effect(() => {
if (!profile && pubkey) {
const ndk = get(ndkInstance);
if (!ndk) return;
const user = ndk.getUser({ pubkey: pubkey ?? undefined });
npub = user.npub;
user.fetchProfile().then((userProfile: NDKUserProfile | null) => {
if (userProfile && !profile) {
// Only update if we don't already have profile data
profile = userProfile;
}
});
}
});
async function handleSignOutClick() {
@ -43,40 +51,46 @@ @@ -43,40 +51,46 @@
}
}
function shortenNpub(long: string | undefined) {
function shortenNpub(long: string | null | undefined) {
if (!long) return "";
return long.slice(0, 8) + "…" + long.slice(-4);
}
</script>
<div class="relative">
{#if profile}
<div class="group">
<button
class="h-6 w-6 rounded-full p-0 border-0 bg-transparent cursor-pointer"
id="profile-avatar"
type="button"
aria-label="Open profile menu"
>
<Avatar
rounded
class="h-6 w-6 cursor-pointer"
src={pfp}
alt={username}
id="profile-avatar"
alt={username || "User"}
/>
{#key username || tag}
</button>
<Popover
placement="bottom"
triggeredBy="#profile-avatar"
class="popover-leather w-[180px]"
trigger="hover"
trigger="click"
>
<div class="flex flex-row justify-between space-x-4">
<div class="flex flex-col">
{#if username}
<h3 class="text-lg font-bold">{username}</h3>
{#if isNav}<h4 class="text-base">@{tag}</h4>{/if}
{:else}
<h3 class="text-lg font-bold">Loading...</h3>
{/if}
<ul class="space-y-2 mt-2">
<li>
<CopyToClipboard
displayText={shortenNpub(npub)}
copyText={npub}
displayText={shortenNpub(npub) || "Loading..."}
copyText={npub || ""}
/>
</li>
<li>
@ -89,6 +103,9 @@ @@ -89,6 +103,9 @@
/><span class="underline">View profile</span>
</button>
</li>
<li>
<NetworkStatus />
</li>
{#if isNav}
<li>
<button
@ -114,7 +131,5 @@ @@ -114,7 +131,5 @@
</div>
</div>
</Popover>
{/key}
</div>
{/if}
</div>

5
src/lib/components/util/ViewPublicationLink.svelte

@ -3,7 +3,8 @@ @@ -3,7 +3,8 @@
import { getMatchingTags } from "$lib/utils/nostrUtils";
import { naddrEncode } from "$lib/utils";
import { getEventType } from "$lib/utils/mime";
import { standardRelays } from "$lib/consts";
import { activeInboxRelays, activeOutboxRelays } from "$lib/ndk";
import { communityRelays } from "$lib/consts";
import { goto } from "$app/navigation";
let { event, className = "" } = $props<{
@ -25,7 +26,7 @@ @@ -25,7 +26,7 @@
return null;
}
try {
return naddrEncode(event, standardRelays);
return naddrEncode(event, $activeInboxRelays);
} catch {
return null;
}

55
src/lib/consts.ts

@ -1,45 +1,48 @@ @@ -1,45 +1,48 @@
export const wikiKind = 30818;
export const indexKind = 30040;
export const zettelKinds = [30041, 30818];
export const communityRelay = "wss://theforest.nostr1.com";
export const profileRelays = [
export const communityRelays = [
"wss://theforest.nostr1.com",
//"wss://theforest.gitcitadel.eu"
];
export const searchRelays = [
"wss://profiles.nostr1.com",
"wss://aggr.nostr.land",
"wss://relay.noswhere.com",
];
export const standardRelays = [
"wss://thecitadel.nostr1.com",
"wss://theforest.nostr1.com",
"wss://profiles.nostr1.com",
// Removed gitcitadel.nostr1.com as it's causing connection issues
//'wss://thecitadel.gitcitadel.eu',
//'wss://theforest.gitcitadel.eu',
"wss://nostr.wine",
];
// Non-auth relays for anonymous users
export const anonymousRelays = [
"wss://thecitadel.nostr1.com",
export const secondaryRelays = [
"wss://theforest.nostr1.com",
"wss://profiles.nostr1.com",
"wss://freelay.sovbit.host",
];
export const fallbackRelays = [
"wss://purplepag.es",
"wss://indexer.coracle.social",
"wss://relay.noswhere.com",
"wss://aggr.nostr.land",
//"wss://theforest.gitcitadel.eu"
"wss://thecitadel.nostr1.com",
//"wss://thecitadel.gitcitadel.eu",
"wss://nostr.land",
"wss://nostr.wine",
"wss://nostr.sovbit.host",
"wss://freelay.sovbit.host",
"wss://nostr21.com",
"wss://greensoul.space",
"wss://relay.damus.io",
"wss://relay.nostr.band",
];
export const anonymousRelays = [
"wss://freelay.sovbit.host",
"wss://thecitadel.nostr1.com"
];
export const lowbandwidthRelays = [
"wss://theforest.nostr1.com",
"wss://thecitadel.nostr1.com",
"wss://aggr.nostr.land"
];
export const localRelays = [
"wss://localhost:8080",
"wss://localhost:4869"
];
export enum FeedType {
StandardRelays = "standard",
CommunityRelays = "standard",
UserRelays = "user",
}

7
src/lib/navigator/EventNetwork/utils/networkBuilder.ts

@ -8,8 +8,9 @@ @@ -8,8 +8,9 @@
import type { NDKEvent } from "@nostr-dev-kit/ndk";
import type { NetworkNode, NetworkLink, GraphData, GraphState } from "../types";
import { nip19 } from "nostr-tools";
import { standardRelays } from "$lib/consts";
import { activeInboxRelays, activeOutboxRelays } from "$lib/ndk";
import { getMatchingTags } from "$lib/utils/nostrUtils";
import { get } from "svelte/store";
// Configuration
const DEBUG = false; // Set to true to enable debug logging
@ -71,13 +72,13 @@ export function createNetworkNode( @@ -71,13 +72,13 @@ export function createNetworkNode(
pubkey: event.pubkey,
identifier: dTag,
kind: event.kind,
relays: standardRelays,
relays: [...get(activeInboxRelays), ...get(activeOutboxRelays)],
});
// Create nevent (NIP-19 event reference) for the event
node.nevent = nip19.neventEncode({
id: event.id,
relays: standardRelays,
relays: [...get(activeInboxRelays), ...get(activeOutboxRelays)],
kind: event.kind,
});
} catch (error) {

405
src/lib/ndk.ts

@ -8,15 +8,29 @@ import NDK, { @@ -8,15 +8,29 @@ import NDK, {
} from "@nostr-dev-kit/ndk";
import { get, writable, type Writable } from "svelte/store";
import {
fallbackRelays,
secondaryRelays,
FeedType,
loginStorageKey,
standardRelays,
communityRelays,
anonymousRelays,
searchRelays,
} from "./consts";
import { feedType } from "./stores";
import {
buildCompleteRelaySet,
testRelayConnection,
discoverLocalRelays,
getUserLocalRelays,
getUserBlockedRelays,
getUserOutboxRelays,
deduplicateRelayUrls,
} from "./utils/relay_management";
// Re-export testRelayConnection for components that need it
export { testRelayConnection };
import { startNetworkMonitoring, NetworkCondition } from "./utils/network_detection";
import { userStore } from "./stores/userStore";
import { userPubkey } from "$lib/stores/authStore.Svelte";
import { startNetworkStatusMonitoring, stopNetworkStatusMonitoring } from "./stores/networkStore";
export const ndkInstance: Writable<NDK> = writable();
export const ndkSignedIn = writable(false);
@ -24,6 +38,10 @@ export const activePubkey = writable<string | null>(null); @@ -24,6 +38,10 @@ export const activePubkey = writable<string | null>(null);
export const inboxRelays = writable<string[]>([]);
export const outboxRelays = writable<string[]>([]);
// New relay management stores
export const activeInboxRelays = writable<string[]>([]);
export const activeOutboxRelays = writable<string[]>([]);
/**
* Custom authentication policy that handles NIP-42 authentication manually
* when the default NDK authentication fails
@ -207,83 +225,7 @@ export function checkWebSocketSupport(): void { @@ -207,83 +225,7 @@ export function checkWebSocketSupport(): void {
}
}
/**
* Tests connection to a relay and returns connection status
* @param relayUrl The relay URL to test
* @param ndk The NDK instance
* @returns Promise that resolves to connection status
*/
export async function testRelayConnection(
relayUrl: string,
ndk: NDK,
): Promise<{
connected: boolean;
requiresAuth: boolean;
error?: string;
actualUrl?: string;
}> {
return new Promise((resolve) => {
console.debug(`[NDK.ts] Testing connection to: ${relayUrl}`);
// Ensure the URL is using wss:// protocol
const secureUrl = ensureSecureWebSocket(relayUrl);
const relay = new NDKRelay(secureUrl, undefined, new NDK());
let authRequired = false;
let connected = false;
let error: string | undefined;
let actualUrl: string | undefined;
const timeout = setTimeout(() => {
relay.disconnect();
resolve({
connected: false,
requiresAuth: authRequired,
error: "Connection timeout",
actualUrl,
});
}, 5000);
relay.on("connect", () => {
console.debug(`[NDK.ts] Connected to ${secureUrl}`);
connected = true;
actualUrl = secureUrl;
clearTimeout(timeout);
relay.disconnect();
resolve({
connected: true,
requiresAuth: authRequired,
error,
actualUrl,
});
});
relay.on("notice", (message: string) => {
if (message.includes("auth-required")) {
authRequired = true;
console.debug(`[NDK.ts] ${secureUrl} requires authentication`);
}
});
relay.on("disconnect", () => {
if (!connected) {
error = "Connection failed";
console.error(`[NDK.ts] Failed to connect to ${secureUrl}`);
clearTimeout(timeout);
resolve({
connected: false,
requiresAuth: authRequired,
error,
actualUrl,
});
}
});
// Log the actual WebSocket URL being used
console.debug(`[NDK.ts] Attempting connection to: ${secureUrl}`);
relay.connect();
});
}
/**
* Gets the user's pubkey from local storage, if it exists.
@ -433,98 +375,180 @@ function createRelayWithAuth(url: string, ndk: NDK): NDKRelay { @@ -433,98 +375,180 @@ function createRelayWithAuth(url: string, ndk: NDK): NDKRelay {
return relay;
}
export function getActiveRelays(ndk: NDK): NDKRelaySet {
/**
* Gets the active relay set for the current user
* @param ndk NDK instance
* @returns Promise that resolves to object with inbox and outbox relay arrays
*/
export async function getActiveRelaySet(ndk: NDK): Promise<{ inboxRelays: string[]; outboxRelays: string[] }> {
const user = get(userStore);
// Filter out problematic relays that are known to cause connection issues
const filterProblematicRelays = (relays: string[]) => {
return relays.filter((relay) => {
// Filter out gitcitadel.nostr1.com which is causing connection issues
if (relay.includes("gitcitadel.nostr1.com")) {
console.warn(`[NDK.ts] Filtering out problematic relay: ${relay}`);
return false;
if (user.signedIn && user.ndkUser) {
return await buildCompleteRelaySet(ndk, user.ndkUser);
} else {
return await buildCompleteRelaySet(ndk, null);
}
}
return true;
});
};
return get(feedType) === FeedType.UserRelays && user.signedIn
? new NDKRelaySet(
new Set(
filterProblematicRelays(user.relays.inbox).map(
(relay) =>
new NDKRelay(relay, NDKRelayAuthPolicies.signIn({ ndk }), ndk),
),
),
ndk,
)
: new NDKRelaySet(
new Set(
filterProblematicRelays(standardRelays).map(
(relay) =>
new NDKRelay(relay, NDKRelayAuthPolicies.signIn({ ndk }), ndk),
),
),
ndk,
/**
* Updates the active relay stores and NDK pool with new relay URLs
* @param ndk NDK instance
*/
export async function updateActiveRelayStores(ndk: NDK): Promise<void> {
try {
// Get the active relay set from the relay management system
const relaySet = await getActiveRelaySet(ndk);
// Update the stores with the new relay configuration
activeInboxRelays.set(relaySet.inboxRelays);
activeOutboxRelays.set(relaySet.outboxRelays);
// Add relays to NDK pool (deduplicated)
const allRelayUrls = deduplicateRelayUrls([...relaySet.inboxRelays, ...relaySet.outboxRelays]);
for (const url of allRelayUrls) {
try {
const relay = createRelayWithAuth(url, ndk);
ndk.pool?.addRelay(relay);
} catch (error) {
// Silently ignore relay addition failures
}
}
} catch (error) {
// Silently ignore relay store update errors
}
}
/**
* Logs the current relay configuration to console
*/
export function logCurrentRelayConfiguration(): void {
const inboxRelays = get(activeInboxRelays);
const outboxRelays = get(activeOutboxRelays);
console.log('🔌 Current Relay Configuration:');
console.log('📥 Inbox Relays:', inboxRelays);
console.log('📤 Outbox Relays:', outboxRelays);
console.log(`📊 Total: ${inboxRelays.length} inbox, ${outboxRelays.length} outbox`);
}
/**
* Updates relay stores when user state changes
* @param ndk NDK instance
*/
export async function refreshRelayStores(ndk: NDK): Promise<void> {
console.debug('[NDK.ts] Refreshing relay stores due to user state change');
await updateActiveRelayStores(ndk);
}
/**
* Updates relay stores when network condition changes
* @param ndk NDK instance
*/
export async function refreshRelayStoresOnNetworkChange(ndk: NDK): Promise<void> {
console.debug('[NDK.ts] Refreshing relay stores due to network condition change');
await updateActiveRelayStores(ndk);
}
/**
* Starts network monitoring for relay optimization
* @param ndk NDK instance
*/
export function startNetworkMonitoringForRelays(ndk: NDK): void {
// Use centralized network monitoring instead of separate monitoring
startNetworkStatusMonitoring();
}
/**
* Creates NDKRelaySet from relay URLs with proper authentication
* @param relayUrls Array of relay URLs
* @param ndk NDK instance
* @returns NDKRelaySet
*/
function createRelaySetFromUrls(relayUrls: string[], ndk: NDK): NDKRelaySet {
const relays = relayUrls.map(url =>
new NDKRelay(url, NDKRelayAuthPolicies.signIn({ ndk }), ndk)
);
return new NDKRelaySet(new Set(relays), ndk);
}
/**
* Initializes an instance of NDK, and connects it to the logged-in user's preferred relay set
* (if available), or to Alexandria's standard relay set.
* @returns The initialized NDK instance.
* Gets the active relay set as NDKRelaySet for use in queries
* @param ndk NDK instance
* @param useInbox Whether to use inbox relays (true) or outbox relays (false)
* @returns Promise that resolves to NDKRelaySet
*/
export function initNdk(): NDK {
const startingPubkey = getPersistedLogin();
const [startingInboxes, _] =
startingPubkey != null
? getPersistedRelays(new NDKUser({ pubkey: startingPubkey }))
: [null, null];
export async function getActiveRelaySetAsNDKRelaySet(
ndk: NDK,
useInbox: boolean = true
): Promise<NDKRelaySet> {
const relaySet = await getActiveRelaySet(ndk);
const urls = useInbox ? relaySet.inboxRelays : relaySet.outboxRelays;
// Ensure all relay URLs use secure WebSocket protocol
const secureRelayUrls = (
startingInboxes != null
? Array.from(startingInboxes.values())
: anonymousRelays
).map(ensureSecureWebSocket);
return createRelaySetFromUrls(urls, ndk);
}
console.debug("[NDK.ts] Initializing NDK with relay URLs:", secureRelayUrls);
/**
* Initializes an instance of NDK with the new relay management system
* @returns The initialized NDK instance
*/
export function initNdk(): NDK {
console.debug("[NDK.ts] Initializing NDK with new relay management system");
const ndk = new NDK({
autoConnectUserRelays: true,
autoConnectUserRelays: false, // We'll manage relays manually
enableOutboxModel: true,
explicitRelayUrls: secureRelayUrls,
});
// Set up custom authentication policy
ndk.relayAuthDefaultPolicy = NDKRelayAuthPolicies.signIn({ ndk });
// Connect with better error handling
ndk
.connect()
.then(() => {
// Connect with better error handling and reduced retry attempts
let retryCount = 0;
const maxRetries = 2;
const attemptConnection = async () => {
try {
await ndk.connect();
console.debug("[NDK.ts] NDK connected successfully");
})
.catch((error) => {
console.error("[NDK.ts] Failed to connect NDK:", error);
// Try to reconnect after a delay
setTimeout(() => {
console.debug("[NDK.ts] Attempting to reconnect...");
ndk.connect().catch((retryError) => {
console.error("[NDK.ts] Reconnection failed:", retryError);
});
}, 5000);
});
// Update relay stores after connection
await updateActiveRelayStores(ndk);
// Start network monitoring for relay optimization
startNetworkMonitoringForRelays(ndk);
} catch (error) {
console.warn("[NDK.ts] Failed to connect NDK:", error);
// Only retry a limited number of times
if (retryCount < maxRetries) {
retryCount++;
console.debug(`[NDK.ts] Attempting to reconnect (${retryCount}/${maxRetries})...`);
setTimeout(attemptConnection, 3000);
} else {
console.warn("[NDK.ts] Max retries reached, continuing with limited functionality");
// Still try to update relay stores even if connection failed
try {
await updateActiveRelayStores(ndk);
startNetworkMonitoringForRelays(ndk);
} catch (storeError) {
console.warn("[NDK.ts] Failed to update relay stores:", storeError);
}
}
}
};
attemptConnection();
return ndk;
}
/**
* Signs in with a NIP-07 browser extension, and determines the user's preferred inbox and outbox
* relays.
* @returns The user's profile, if it is available.
* @throws If sign-in fails. This may because there is no accessible NIP-07 extension, or because
* NDK is unable to fetch the user's profile or relay lists.
* Signs in with a NIP-07 browser extension using the new relay management system
* @returns The user's profile, if it is available
* @throws If sign-in fails
*/
export async function loginWithExtension(
pubkey?: string,
@ -542,23 +566,10 @@ export async function loginWithExtension( @@ -542,23 +566,10 @@ export async function loginWithExtension(
activePubkey.set(signerUser.pubkey);
userPubkey.set(signerUser.pubkey);
const [persistedInboxes, persistedOutboxes] =
getPersistedRelays(signerUser);
for (const relay of persistedInboxes) {
ndk.addExplicitRelay(relay);
}
const user = ndk.getUser({ pubkey: signerUser.pubkey });
const [inboxes, outboxes] = await getUserPreferredRelays(ndk, user);
inboxRelays.set(
Array.from(inboxes ?? persistedInboxes).map((relay) => relay.url),
);
outboxRelays.set(
Array.from(outboxes ?? persistedOutboxes).map((relay) => relay.url),
);
persistRelays(signerUser, inboxes, outboxes);
// Update relay stores with the new system
await updateActiveRelayStores(ndk);
ndk.signer = signer;
ndk.activeUser = user;
@ -582,73 +593,17 @@ export function logout(user: NDKUser): void { @@ -582,73 +593,17 @@ export function logout(user: NDKUser): void {
activePubkey.set(null);
userPubkey.set(null);
ndkSignedIn.set(false);
ndkInstance.set(initNdk()); // Re-initialize with anonymous instance
}
/**
* Fetches the user's NIP-65 relay list, if one can be found, and returns the inbox and outbox
* relay sets.
* @returns A tuple of relay sets of the form `[inboxRelays, outboxRelays]`.
*/
export async function getUserPreferredRelays(
ndk: NDK,
user: NDKUser,
fallbacks: readonly string[] = fallbackRelays,
): Promise<[Set<NDKRelay>, Set<NDKRelay>]> {
const relayList = await ndk.fetchEvent(
{
kinds: [10002],
authors: [user.pubkey],
},
{
groupable: false,
skipVerification: false,
skipValidation: false,
},
NDKRelaySet.fromRelayUrls(fallbacks, ndk),
);
// Clear relay stores
activeInboxRelays.set([]);
activeOutboxRelays.set([]);
const inboxRelays = new Set<NDKRelay>();
const outboxRelays = new Set<NDKRelay>();
// Stop network monitoring
stopNetworkStatusMonitoring();
// Filter out problematic relays
const filterProblematicRelay = (url: string): boolean => {
if (url.includes("gitcitadel.nostr1.com")) {
console.warn(
`[NDK.ts] Filtering out problematic relay from user preferences: ${url}`,
);
return false;
// Re-initialize with anonymous instance
const newNdk = initNdk();
ndkInstance.set(newNdk);
}
return true;
};
if (relayList == null) {
const relayMap = await window.nostr?.getRelays?.();
Object.entries(relayMap ?? {}).forEach(([url, relayType]) => {
if (filterProblematicRelay(url)) {
const relay = createRelayWithAuth(url, ndk);
if (relayType.read) inboxRelays.add(relay);
if (relayType.write) outboxRelays.add(relay);
}
});
} else {
relayList.tags.forEach((tag) => {
if (filterProblematicRelay(tag[1])) {
switch (tag[0]) {
case "r":
inboxRelays.add(createRelayWithAuth(tag[1], ndk));
break;
case "w":
outboxRelays.add(createRelayWithAuth(tag[1], ndk));
break;
default:
inboxRelays.add(createRelayWithAuth(tag[1], ndk));
outboxRelays.add(createRelayWithAuth(tag[1], ndk));
break;
}
}
});
}
return [inboxRelays, outboxRelays];
}

10
src/lib/stores.ts

@ -1,11 +1,11 @@ @@ -1,11 +1,11 @@
import { readable, writable } from "svelte/store";
import { FeedType } from "./consts.ts";
import { writable } from "svelte/store";
export let idList = writable<string[]>([]);
// The old feedType store is no longer needed since we use the new relay management system
// All relay selection is now handled by the activeInboxRelays and activeOutboxRelays stores in ndk.ts
export let alexandriaKinds = readable<number[]>([30040, 30041, 30818]);
export let idList = writable<string[]>([]);
export let feedType = writable<FeedType>(FeedType.StandardRelays);
export let alexandriaKinds = writable<number[]>([30040, 30041, 30818]);
export interface PublicationLayoutVisibility {
toc: boolean;

55
src/lib/stores/networkStore.ts

@ -0,0 +1,55 @@ @@ -0,0 +1,55 @@
import { writable, type Writable } from 'svelte/store';
import { detectNetworkCondition, NetworkCondition, startNetworkMonitoring } from '$lib/utils/network_detection';
// Network status store
export const networkCondition = writable<NetworkCondition>(NetworkCondition.ONLINE);
export const isNetworkChecking = writable<boolean>(false);
// Network monitoring state
let stopNetworkMonitoring: (() => void) | null = null;
/**
* Starts network monitoring if not already running
*/
export function startNetworkStatusMonitoring(): void {
if (stopNetworkMonitoring) {
return; // Already monitoring
}
console.debug('[networkStore.ts] Starting network status monitoring');
stopNetworkMonitoring = startNetworkMonitoring(
(condition: NetworkCondition) => {
console.debug(`[networkStore.ts] Network condition changed to: ${condition}`);
networkCondition.set(condition);
},
60000 // Check every 60 seconds to reduce spam
);
}
/**
* Stops network monitoring
*/
export function stopNetworkStatusMonitoring(): void {
if (stopNetworkMonitoring) {
console.debug('[networkStore.ts] Stopping network status monitoring');
stopNetworkMonitoring();
stopNetworkMonitoring = null;
}
}
/**
* Manually check network status (for immediate updates)
*/
export async function checkNetworkStatus(): Promise<void> {
try {
isNetworkChecking.set(true);
const condition = await detectNetworkCondition();
networkCondition.set(condition);
} catch (error) {
console.warn('[networkStore.ts] Failed to check network status:', error);
networkCondition.set(NetworkCondition.OFFLINE);
} finally {
isNetworkChecking.set(false);
}
}

4
src/lib/stores/relayStore.ts

@ -1,4 +0,0 @@ @@ -1,4 +0,0 @@
import { writable } from "svelte/store";
// Initialize with empty array, will be populated from user preferences
export const userRelays = writable<string[]>([]);

6
src/lib/stores/userStore.ts

@ -8,8 +8,8 @@ import { @@ -8,8 +8,8 @@ import {
NDKRelay,
} from "@nostr-dev-kit/ndk";
import { getUserMetadata } from "$lib/utils/nostrUtils";
import { ndkInstance } from "$lib/ndk";
import { loginStorageKey, fallbackRelays } from "$lib/consts";
import { ndkInstance, activeInboxRelays, activeOutboxRelays } from "$lib/ndk";
import { loginStorageKey } from "$lib/consts";
import { nip19 } from "nostr-tools";
export interface UserState {
@ -70,7 +70,7 @@ function getPersistedRelays(user: NDKUser): [Set<string>, Set<string>] { @@ -70,7 +70,7 @@ function getPersistedRelays(user: NDKUser): [Set<string>, Set<string>] {
async function getUserPreferredRelays(
ndk: any,
user: NDKUser,
fallbacks: readonly string[] = fallbackRelays,
fallbacks: readonly string[] = [...get(activeInboxRelays), ...get(activeOutboxRelays)],
): Promise<[Set<NDKRelay>, Set<NDKRelay>]> {
const relayList = await ndk.fetchEvent(
{

2
src/lib/utils/ZettelParser.ts

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
import { ndkInstance } from "$lib/ndk";
import { signEvent, getEventHash } from "$lib/utils/nostrUtils";
import { getMimeTags } from "$lib/utils/mime";
import { standardRelays } from "$lib/consts";
import { communityRelays } from "$lib/consts";
import { nip19 } from "nostr-tools";
export interface ZettelSection {

22
src/lib/utils/community_checker.ts

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
import { communityRelay } from "$lib/consts";
import { communityRelays } from "$lib/consts";
import { RELAY_CONSTANTS, SEARCH_LIMITS } from "./search_constants";
// Cache for pubkeys with kind 1 events on communityRelay
@ -13,9 +13,11 @@ export async function checkCommunity(pubkey: string): Promise<boolean> { @@ -13,9 +13,11 @@ export async function checkCommunity(pubkey: string): Promise<boolean> {
}
try {
const relayUrl = communityRelay;
// Try each community relay until we find one that works
for (const relayUrl of communityRelays) {
try {
const ws = new WebSocket(relayUrl);
return await new Promise((resolve) => {
const result = await new Promise<boolean>((resolve) => {
ws.onopen = () => {
ws.send(
JSON.stringify([
@ -42,11 +44,23 @@ export async function checkCommunity(pubkey: string): Promise<boolean> { @@ -42,11 +44,23 @@ export async function checkCommunity(pubkey: string): Promise<boolean> {
}
};
ws.onerror = () => {
communityCache.set(pubkey, false);
ws.close();
resolve(false);
};
});
if (result) {
return true;
}
} catch {
// Continue to next relay if this one fails
continue;
}
}
// If we get here, no relay found the user
communityCache.set(pubkey, false);
return false;
} catch {
communityCache.set(pubkey, false);
return false;

189
src/lib/utils/network_detection.ts

@ -0,0 +1,189 @@ @@ -0,0 +1,189 @@
import { deduplicateRelayUrls } from './relay_management';
/**
* Network conditions for relay selection
*/
export enum NetworkCondition {
ONLINE = 'online',
SLOW = 'slow',
OFFLINE = 'offline'
}
/**
* Network connectivity test endpoints
*/
const NETWORK_ENDPOINTS = [
'https://www.google.com/favicon.ico',
'https://httpbin.org/status/200',
'https://api.github.com/zen'
];
/**
* Detects if the network is online using more reliable endpoints
* @returns Promise that resolves to true if online, false otherwise
*/
export async function isNetworkOnline(): Promise<boolean> {
for (const endpoint of NETWORK_ENDPOINTS) {
try {
// Use a simple fetch without HEAD method to avoid CORS issues
const response = await fetch(endpoint, {
method: 'GET',
cache: 'no-cache',
signal: AbortSignal.timeout(3000),
mode: 'no-cors' // Use no-cors mode to avoid CORS issues
});
// With no-cors mode, we can't check response.ok, so we assume success if no error
return true;
} catch (error) {
console.debug(`[network_detection.ts] Failed to reach ${endpoint}:`, error);
continue;
}
}
console.debug('[network_detection.ts] All network endpoints failed');
return false;
}
/**
* Tests network speed by measuring response time
* @returns Promise that resolves to network speed in milliseconds
*/
export async function testNetworkSpeed(): Promise<number> {
const startTime = performance.now();
for (const endpoint of NETWORK_ENDPOINTS) {
try {
await fetch(endpoint, {
method: 'GET',
cache: 'no-cache',
signal: AbortSignal.timeout(5000),
mode: 'no-cors' // Use no-cors mode to avoid CORS issues
});
const endTime = performance.now();
return endTime - startTime;
} catch (error) {
console.debug(`[network_detection.ts] Speed test failed for ${endpoint}:`, error);
continue;
}
}
console.debug('[network_detection.ts] Network speed test failed for all endpoints');
return Infinity; // Very slow if it fails
}
/**
* Determines network condition based on connectivity and speed
* @returns Promise that resolves to NetworkCondition
*/
export async function detectNetworkCondition(): Promise<NetworkCondition> {
const isOnline = await isNetworkOnline();
if (!isOnline) {
console.debug('[network_detection.ts] Network condition: OFFLINE');
return NetworkCondition.OFFLINE;
}
const speed = await testNetworkSpeed();
// Consider network slow if response time > 2000ms
if (speed > 2000) {
console.debug(`[network_detection.ts] Network condition: SLOW (${speed.toFixed(0)}ms)`);
return NetworkCondition.SLOW;
}
console.debug(`[network_detection.ts] Network condition: ONLINE (${speed.toFixed(0)}ms)`);
return NetworkCondition.ONLINE;
}
/**
* Gets the appropriate relay sets based on network condition
* @param networkCondition The detected network condition
* @param discoveredLocalRelays Array of discovered local relay URLs
* @param lowbandwidthRelays Array of low bandwidth relay URLs
* @param fullRelaySet The complete relay set for normal conditions
* @returns Object with inbox and outbox relay arrays
*/
export function getRelaySetForNetworkCondition(
networkCondition: NetworkCondition,
discoveredLocalRelays: string[],
lowbandwidthRelays: string[],
fullRelaySet: { inboxRelays: string[]; outboxRelays: string[] }
): { inboxRelays: string[]; outboxRelays: string[] } {
switch (networkCondition) {
case NetworkCondition.OFFLINE:
// When offline, use local relays if available, otherwise rely on cache
// This will be improved when IndexedDB local relay is implemented
if (discoveredLocalRelays.length > 0) {
console.debug('[network_detection.ts] Using local relays (offline)');
return {
inboxRelays: discoveredLocalRelays,
outboxRelays: discoveredLocalRelays
};
} else {
console.debug('[network_detection.ts] No local relays available, will rely on cache (offline)');
return {
inboxRelays: [],
outboxRelays: []
};
}
case NetworkCondition.SLOW:
// Local relays + low bandwidth relays when slow (deduplicated)
console.debug('[network_detection.ts] Using local + low bandwidth relays (slow network)');
const slowInboxRelays = deduplicateRelayUrls([...discoveredLocalRelays, ...lowbandwidthRelays]);
const slowOutboxRelays = deduplicateRelayUrls([...discoveredLocalRelays, ...lowbandwidthRelays]);
return {
inboxRelays: slowInboxRelays,
outboxRelays: slowOutboxRelays
};
case NetworkCondition.ONLINE:
default:
// Full relay set when online
console.debug('[network_detection.ts] Using full relay set (online)');
return fullRelaySet;
}
}
/**
* Starts periodic network monitoring with reduced frequency to avoid spam
* @param onNetworkChange Callback function called when network condition changes
* @param checkInterval Interval in milliseconds between network checks (default: 60 seconds)
* @returns Function to stop the monitoring
*/
export function startNetworkMonitoring(
onNetworkChange: (condition: NetworkCondition) => void,
checkInterval: number = 60000 // Increased to 60 seconds to reduce spam
): () => void {
let lastCondition: NetworkCondition | null = null;
let intervalId: number | null = null;
const checkNetwork = async () => {
try {
const currentCondition = await detectNetworkCondition();
if (currentCondition !== lastCondition) {
console.debug(`[network_detection.ts] Network condition changed: ${lastCondition} -> ${currentCondition}`);
lastCondition = currentCondition;
onNetworkChange(currentCondition);
}
} catch (error) {
console.warn('[network_detection.ts] Network monitoring error:', error);
}
};
// Initial check
checkNetwork();
// Set up periodic monitoring
intervalId = window.setInterval(checkNetwork, checkInterval);
// Return function to stop monitoring
return () => {
if (intervalId !== null) {
clearInterval(intervalId);
intervalId = null;
}
};
}

104
src/lib/utils/nostrEventService.ts

@ -1,11 +1,13 @@ @@ -1,11 +1,13 @@
import { nip19 } from "nostr-tools";
import { getEventHash, signEvent, prefixNostrAddresses } from "./nostrUtils";
import { standardRelays, fallbackRelays } from "$lib/consts";
import { userRelays } from "$lib/stores/relayStore";
import { communityRelays, secondaryRelays } from "$lib/consts";
import { get } from "svelte/store";
import { goto } from "$app/navigation";
import type { NDKEvent } from "./nostrUtils";
import { EVENT_KINDS, TIME_CONSTANTS, TIMEOUTS } from "./search_constants";
import { activeInboxRelays, activeOutboxRelays } from "$lib/ndk";
import { ndkInstance } from "$lib/ndk";
import { NDKRelaySet } from "@nostr-dev-kit/ndk";
export interface RootEventInfo {
rootId: string;
@ -358,82 +360,44 @@ export async function createSignedEvent( @@ -358,82 +360,44 @@ export async function createSignedEvent(
}
/**
* Publish event to a single relay
* Publishes an event to relays using the new relay management system
* @param event The event to publish
* @param relayUrls Array of relay URLs to publish to
* @returns Promise that resolves to array of successful relay URLs
*/
async function publishToRelay(
relayUrl: string,
signedEvent: any,
): Promise<void> {
const ws = new WebSocket(relayUrl);
return new Promise<void>((resolve, reject) => {
const timeout = setTimeout(() => {
ws.close();
reject(new Error("Timeout"));
}, TIMEOUTS.GENERAL);
ws.onopen = () => {
ws.send(JSON.stringify(["EVENT", signedEvent]));
};
export async function publishEvent(
event: NDKEvent,
relayUrls: string[],
): Promise<string[]> {
const successfulRelays: string[] = [];
const ndk = get(ndkInstance);
ws.onmessage = (e) => {
const [type, id, ok, message] = JSON.parse(e.data);
if (type === "OK" && id === signedEvent.id) {
clearTimeout(timeout);
if (ok) {
ws.close();
resolve();
} else {
ws.close();
reject(new Error(message));
if (!ndk) {
throw new Error("NDK instance not available");
}
}
};
ws.onerror = () => {
clearTimeout(timeout);
ws.close();
reject(new Error("WebSocket error"));
};
});
}
// Create relay set from URLs
const relaySet = NDKRelaySet.fromRelayUrls(relayUrls, ndk);
/**
* Publish event to relays
*/
export async function publishEvent(
signedEvent: any,
useOtherRelays = false,
useFallbackRelays = false,
userRelayPreference = false,
): Promise<EventPublishResult> {
// Determine which relays to use
let relays = userRelayPreference ? get(userRelays) : standardRelays;
if (useOtherRelays) {
relays = userRelayPreference ? standardRelays : get(userRelays);
}
if (useFallbackRelays) {
relays = fallbackRelays;
}
// Try to publish to relays
for (const relayUrl of relays) {
try {
await publishToRelay(relayUrl, signedEvent);
return {
success: true,
relay: relayUrl,
eventId: signedEvent.id,
};
} catch (e) {
console.error(`Failed to publish to ${relayUrl}:`, e);
}
// Publish with timeout
await event.publish(relaySet).withTimeout(10000);
// For now, assume all relays were successful
// In a more sophisticated implementation, you'd track individual relay responses
successfulRelays.push(...relayUrls);
console.debug("[nostrEventService] Published event successfully:", {
eventId: event.id,
relayCount: relayUrls.length,
successfulRelays
});
} catch (error) {
console.error("[nostrEventService] Failed to publish event:", error);
throw new Error(`Failed to publish event: ${error}`);
}
return {
success: false,
error: "Failed to publish to any relays",
};
return successfulRelays;
}
/**

83
src/lib/utils/nostrUtils.ts

@ -4,7 +4,8 @@ import { ndkInstance } from "$lib/ndk"; @@ -4,7 +4,8 @@ import { ndkInstance } from "$lib/ndk";
import { npubCache } from "./npubCache";
import NDK, { NDKEvent, NDKRelaySet, NDKUser } from "@nostr-dev-kit/ndk";
import type { NDKFilter, NDKKind } from "@nostr-dev-kit/ndk";
import { standardRelays, fallbackRelays, anonymousRelays } from "$lib/consts";
import { communityRelays, secondaryRelays, anonymousRelays } from "$lib/consts";
import { activeInboxRelays } from "$lib/ndk";
import { NDKRelaySet as NDKRelaySetFromNDK } from "@nostr-dev-kit/ndk";
import { sha256 } from "@noble/hashes/sha256";
import { schnorr } from "@noble/curves/secp256k1";
@ -174,9 +175,9 @@ export async function createProfileLinkWithVerification( @@ -174,9 +175,9 @@ export async function createProfileLinkWithVerification(
};
const allRelays = [
...standardRelays,
...communityRelays,
...userRelays,
...fallbackRelays,
...secondaryRelays,
].filter((url, idx, arr) => arr.indexOf(url) === idx);
const filteredRelays = filterProblematicRelays(allRelays);
@ -422,42 +423,25 @@ export async function fetchEventWithFallback( @@ -422,42 +423,25 @@ export async function fetchEventWithFallback(
filterOrId: string | NDKFilter<NDKKind>,
timeoutMs: number = 3000,
): Promise<NDKEvent | null> {
// Get user relays if logged in
const userRelays = ndk.activeUser
? Array.from(ndk.pool?.relays.values() || [])
.filter((r) => r.status === 1) // Only use connected relays
.map((r) => r.url)
.filter((url) => !url.includes("gitcitadel.nostr1.com")) // Filter out problematic relay
: [];
// Determine which relays to use based on user authentication status
const isSignedIn = ndk.signer && ndk.activeUser;
const primaryRelays = isSignedIn ? standardRelays : anonymousRelays;
// Create three relay sets in priority order
const relaySets = [
NDKRelaySetFromNDK.fromRelayUrls(primaryRelays, ndk), // 1. Primary relays (auth or anonymous)
NDKRelaySetFromNDK.fromRelayUrls(userRelays, ndk), // 2. User relays (if logged in)
NDKRelaySetFromNDK.fromRelayUrls(fallbackRelays, ndk), // 3. fallback relays (last resort)
];
// Use the active inbox relays from the relay management system
const inboxRelays = get(activeInboxRelays);
// Create relay set from active inbox relays
const relaySet = NDKRelaySetFromNDK.fromRelayUrls(inboxRelays, ndk);
try {
let found: NDKEvent | null = null;
const triedRelaySets: string[] = [];
if (relaySet.relays.size === 0) {
console.warn("No inbox relays available for event fetch");
return null;
}
// Helper function to try fetching from a relay set
async function tryFetchFromRelaySet(
relaySet: NDKRelaySetFromNDK,
setName: string,
): Promise<NDKEvent | null> {
if (relaySet.relays.size === 0) return null;
triedRelaySets.push(setName);
let found: NDKEvent | null = null;
if (
typeof filterOrId === "string" &&
new RegExp(`^[0-9a-f]{${VALIDATION.HEX_LENGTH}}$`, "i").test(filterOrId)
) {
return await ndk
found = await ndk
.fetchEvent({ ids: [filterOrId] }, undefined, relaySet)
.withTimeout(timeoutMs);
} else {
@ -466,47 +450,16 @@ export async function fetchEventWithFallback( @@ -466,47 +450,16 @@ export async function fetchEventWithFallback(
const results = await ndk
.fetchEvents(filter, undefined, relaySet)
.withTimeout(timeoutMs);
return results instanceof Set
found = results instanceof Set
? (Array.from(results)[0] as NDKEvent)
: null;
}
}
// Try each relay set in order
for (const [index, relaySet] of relaySets.entries()) {
const setName =
index === 0
? isSignedIn
? "standard relays"
: "anonymous relays"
: index === 1
? "user relays"
: "fallback relays";
found = await tryFetchFromRelaySet(relaySet, setName);
if (found) break;
}
if (!found) {
const timeoutSeconds = timeoutMs / 1000;
const relayUrls = relaySets
.map((set, i) => {
const setName =
i === 0
? isSignedIn
? "standard relays"
: "anonymous relays"
: i === 1
? "user relays"
: "fallback relays";
const urls = Array.from(set.relays).map((r) => r.url);
return urls.length > 0 ? `${setName} (${urls.join(", ")})` : null;
})
.filter(Boolean)
.join(", then ");
const relayUrls = Array.from(relaySet.relays).map((r: any) => r.url).join(", ");
console.warn(
`Event not found after ${timeoutSeconds}s timeout. Tried ${relayUrls}. Some relays may be offline or slow.`,
`Event not found after ${timeoutSeconds}s timeout. Tried inbox relays: ${relayUrls}. Some relays may be offline or slow.`,
);
return null;
}

4
src/lib/utils/profile_search.ts

@ -2,7 +2,7 @@ import { ndkInstance } from "$lib/ndk"; @@ -2,7 +2,7 @@ import { ndkInstance } from "$lib/ndk";
import { getUserMetadata, getNpubFromNip05 } from "$lib/utils/nostrUtils";
import { NDKRelaySet, NDKEvent } from "@nostr-dev-kit/ndk";
import { searchCache } from "$lib/utils/searchCache";
import { standardRelays, fallbackRelays } from "$lib/consts";
import { communityRelays, secondaryRelays } from "$lib/consts";
import { get } from "svelte/store";
import type { NostrProfile, ProfileSearchResult } from "./search_types";
import {
@ -270,7 +270,7 @@ async function quickRelaySearch( @@ -270,7 +270,7 @@ async function quickRelaySearch(
console.log("Normalized search term for relay search:", normalizedSearchTerm);
// Use all profile relays for better coverage
const quickRelayUrls = [...standardRelays, ...fallbackRelays]; // Use all available relays
const quickRelayUrls = [...communityRelays, ...secondaryRelays]; // Use all available relays
console.log("Using all relays for search:", quickRelayUrls);
// Create relay sets for parallel search

8
src/lib/utils/relayDiagnostics.ts

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
import { standardRelays, anonymousRelays, fallbackRelays } from "$lib/consts";
import { activeInboxRelays, activeOutboxRelays } from "$lib/ndk";
import NDK from "@nostr-dev-kit/ndk";
import { TIMEOUTS } from "./search_constants";
import { get } from "svelte/store";
export interface RelayDiagnostic {
url: string;
@ -85,9 +86,8 @@ export async function testRelay(url: string): Promise<RelayDiagnostic> { @@ -85,9 +86,8 @@ export async function testRelay(url: string): Promise<RelayDiagnostic> {
* Tests all relays and returns diagnostic information
*/
export async function testAllRelays(): Promise<RelayDiagnostic[]> {
const allRelays = [
...new Set([...standardRelays, ...anonymousRelays, ...fallbackRelays]),
];
// Use the new relay management system
const allRelays = [...get(activeInboxRelays), ...get(activeOutboxRelays)];
console.log("[RelayDiagnostics] Testing", allRelays.length, "relays...");

424
src/lib/utils/relay_management.ts

@ -0,0 +1,424 @@ @@ -0,0 +1,424 @@
import NDK, { NDKRelay, NDKUser } from "@nostr-dev-kit/ndk";
import { communityRelays, searchRelays, secondaryRelays, anonymousRelays, lowbandwidthRelays, localRelays } from "../consts";
import { getRelaySetForNetworkCondition, NetworkCondition } from "./network_detection";
import { networkCondition } from "../stores/networkStore";
import { get } from "svelte/store";
/**
* Normalizes a relay URL to a standard format
* @param url The relay URL to normalize
* @returns The normalized relay URL
*/
export function normalizeRelayUrl(url: string): string {
let normalized = url.toLowerCase().trim();
// Ensure protocol is present
if (!normalized.startsWith('ws://') && !normalized.startsWith('wss://')) {
normalized = 'wss://' + normalized;
}
// Remove trailing slash
normalized = normalized.replace(/\/$/, '');
return normalized;
}
/**
* Normalizes an array of relay URLs
* @param urls Array of relay URLs to normalize
* @returns Array of normalized relay URLs
*/
export function normalizeRelayUrls(urls: string[]): string[] {
return urls.map(normalizeRelayUrl);
}
/**
* Removes duplicates from an array of relay URLs
* @param urls Array of relay URLs
* @returns Array of unique relay URLs
*/
export function deduplicateRelayUrls(urls: string[]): string[] {
const normalized = normalizeRelayUrls(urls);
return [...new Set(normalized)];
}
/**
* Tests connection to a relay and returns connection status
* @param relayUrl The relay URL to test
* @param ndk The NDK instance
* @returns Promise that resolves to connection status
*/
export async function testRelayConnection(
relayUrl: string,
ndk: NDK,
): Promise<{
connected: boolean;
requiresAuth: boolean;
error?: string;
actualUrl?: string;
}> {
return new Promise((resolve) => {
// Ensure the URL is using wss:// protocol
const secureUrl = ensureSecureWebSocket(relayUrl);
// Use the existing NDK instance instead of creating a new one
const relay = new NDKRelay(secureUrl, undefined, ndk);
let authRequired = false;
let connected = false;
let error: string | undefined;
let actualUrl: string | undefined;
const timeout = setTimeout(() => {
relay.disconnect();
resolve({
connected: false,
requiresAuth: authRequired,
error: "Connection timeout",
actualUrl,
});
}, 3000); // Increased timeout to 3 seconds to give relays more time
relay.on("connect", () => {
connected = true;
actualUrl = secureUrl;
clearTimeout(timeout);
relay.disconnect();
resolve({
connected: true,
requiresAuth: authRequired,
error,
actualUrl,
});
});
relay.on("notice", (message: string) => {
if (message.includes("auth-required")) {
authRequired = true;
}
});
relay.on("disconnect", () => {
if (!connected) {
error = "Connection failed";
clearTimeout(timeout);
resolve({
connected: false,
requiresAuth: authRequired,
error,
actualUrl,
});
}
});
relay.connect();
});
}
/**
* Ensures a relay URL uses secure WebSocket protocol for remote relays
* @param url The relay URL to secure
* @returns The URL with wss:// protocol (except for localhost)
*/
function ensureSecureWebSocket(url: string): string {
// For localhost, always use ws:// (never wss://)
if (url.includes('localhost') || url.includes('127.0.0.1')) {
// Convert any wss://localhost to ws://localhost
return url.replace(/^wss:\/\//, "ws://");
}
// Replace ws:// with wss:// for remote relays
const secureUrl = url.replace(/^ws:\/\//, "wss://");
if (secureUrl !== url) {
console.warn(
`[relay_management.ts] Protocol upgrade for rem ote relay: ${url} -> ${secureUrl}`,
);
}
return secureUrl;
}
/**
* Tests connection to local relays
* @param localRelayUrls Array of local relay URLs to test
* @param ndk NDK instance
* @returns Promise that resolves to array of working local relay URLs
*/
async function testLocalRelays(localRelayUrls: string[], ndk: NDK): Promise<string[]> {
const workingRelays: string[] = [];
await Promise.all(
localRelayUrls.map(async (url) => {
try {
const result = await testRelayConnection(url, ndk);
if (result.connected) {
workingRelays.push(url);
}
} catch (error) {
// Silently ignore local relay failures
}
})
);
return workingRelays;
}
/**
* Discovers local relays by testing common localhost URLs
* @param ndk NDK instance
* @returns Promise that resolves to array of working local relay URLs
*/
export async function discoverLocalRelays(ndk: NDK): Promise<string[]> {
try {
// Convert wss:// URLs from consts to ws:// for local testing
const localRelayUrls = localRelays.map(url =>
url.replace(/^wss:\/\//, 'ws://')
);
const workingRelays = await testLocalRelays(localRelayUrls, ndk);
// If no local relays are working, return empty array
// The network detection logic will provide fallback relays
return workingRelays;
} catch (error) {
// Silently fail and return empty array
return [];
}
}
/**
* Fetches user's local relays from kind 10432 event
* @param ndk NDK instance
* @param user User to fetch local relays for
* @returns Promise that resolves to array of local relay URLs
*/
export async function getUserLocalRelays(ndk: NDK, user: NDKUser): Promise<string[]> {
try {
const localRelayEvent = await ndk.fetchEvent(
{
kinds: [10432 as any],
authors: [user.pubkey],
},
{
groupable: false,
skipVerification: false,
skipValidation: false,
}
);
if (!localRelayEvent) {
return [];
}
const localRelays: string[] = [];
localRelayEvent.tags.forEach((tag) => {
if (tag[0] === 'r' && tag[1]) {
localRelays.push(tag[1]);
}
});
return localRelays;
} catch (error) {
console.info('[relay_management.ts] Error fetching user local relays:', error);
return [];
}
}
/**
* Fetches user's blocked relays from kind 10006 event
* @param ndk NDK instance
* @param user User to fetch blocked relays for
* @returns Promise that resolves to array of blocked relay URLs
*/
export async function getUserBlockedRelays(ndk: NDK, user: NDKUser): Promise<string[]> {
try {
const blockedRelayEvent = await ndk.fetchEvent(
{
kinds: [10006],
authors: [user.pubkey],
},
{
groupable: false,
skipVerification: false,
skipValidation: false,
}
);
if (!blockedRelayEvent) {
return [];
}
const blockedRelays: string[] = [];
blockedRelayEvent.tags.forEach((tag) => {
if (tag[0] === 'r' && tag[1]) {
blockedRelays.push(tag[1]);
}
});
return blockedRelays;
} catch (error) {
console.info('[relay_management.ts] Error fetching user blocked relays:', error);
return [];
}
}
/**
* Fetches user's outbox relays from NIP-65 relay list
* @param ndk NDK instance
* @param user User to fetch outbox relays for
* @returns Promise that resolves to array of outbox relay URLs
*/
export async function getUserOutboxRelays(ndk: NDK, user: NDKUser): Promise<string[]> {
try {
const relayList = await ndk.fetchEvent(
{
kinds: [10002],
authors: [user.pubkey],
},
{
groupable: false,
skipVerification: false,
skipValidation: false,
}
);
if (!relayList) {
return [];
}
const outboxRelays: string[] = [];
relayList.tags.forEach((tag) => {
if (tag[0] === 'w' && tag[1]) {
outboxRelays.push(tag[1]);
}
});
return outboxRelays;
} catch (error) {
console.info('[relay_management.ts] Error fetching user outbox relays:', error);
return [];
}
}
/**
* Tests a set of relays in batches to avoid overwhelming them
* @param relayUrls Array of relay URLs to test
* @param ndk NDK instance
* @returns Promise that resolves to array of working relay URLs
*/
async function testRelaySet(relayUrls: string[], ndk: NDK): Promise<string[]> {
const workingRelays: string[] = [];
const maxConcurrent = 3; // Test 3 relays at a time to avoid overwhelming them
for (let i = 0; i < relayUrls.length; i += maxConcurrent) {
const batch = relayUrls.slice(i, i + maxConcurrent);
const batchPromises = batch.map(async (url) => {
try {
const result = await testRelayConnection(url, ndk);
return result.connected ? url : null;
} catch (error) {
return null;
}
});
const batchResults = await Promise.all(batchPromises);
const batchWorkingRelays = batchResults.filter((url): url is string => url !== null);
workingRelays.push(...batchWorkingRelays);
}
return workingRelays;
}
/**
* Builds a complete relay set for a user, including local, user-specific, and fallback relays
* @param ndk NDK instance
* @param user NDKUser or null for anonymous access
* @returns Promise that resolves to inbox and outbox relay arrays
*/
export async function buildCompleteRelaySet(
ndk: NDK,
user: NDKUser | null
): Promise<{ inboxRelays: string[]; outboxRelays: string[] }> {
// Discover local relays first
const discoveredLocalRelays = await discoverLocalRelays(ndk);
// Get user-specific relays if available
let userOutboxRelays: string[] = [];
let userLocalRelays: string[] = [];
let blockedRelays: string[] = [];
if (user) {
try {
userOutboxRelays = await getUserOutboxRelays(ndk, user);
} catch (error) {
// Silently ignore user relay fetch errors
}
try {
userLocalRelays = await getUserLocalRelays(ndk, user);
} catch (error) {
// Silently ignore user local relay fetch errors
}
try {
blockedRelays = await getUserBlockedRelays(ndk, user);
} catch (error) {
// Silently ignore blocked relay fetch errors
}
}
// Build initial relay sets and deduplicate
const finalInboxRelays = deduplicateRelayUrls([...discoveredLocalRelays, ...userLocalRelays]);
const finalOutboxRelays = deduplicateRelayUrls([...discoveredLocalRelays, ...userOutboxRelays]);
// Test relays and filter out non-working ones
let testedInboxRelays: string[] = [];
let testedOutboxRelays: string[] = [];
if (finalInboxRelays.length > 0) {
testedInboxRelays = await testRelaySet(finalInboxRelays, ndk);
}
if (finalOutboxRelays.length > 0) {
testedOutboxRelays = await testRelaySet(finalOutboxRelays, ndk);
}
// If no relays passed testing, use remote relays without testing
if (testedInboxRelays.length === 0 && testedOutboxRelays.length === 0) {
const remoteRelays = deduplicateRelayUrls([...secondaryRelays, ...searchRelays]);
return {
inboxRelays: remoteRelays,
outboxRelays: remoteRelays
};
}
// Use tested relays and deduplicate
const inboxRelays = testedInboxRelays.length > 0 ? deduplicateRelayUrls(testedInboxRelays) : deduplicateRelayUrls(secondaryRelays);
const outboxRelays = testedOutboxRelays.length > 0 ? deduplicateRelayUrls(testedOutboxRelays) : deduplicateRelayUrls(secondaryRelays);
// Apply network condition optimization
const currentNetworkCondition = get(networkCondition);
const networkOptimizedRelaySet = getRelaySetForNetworkCondition(
currentNetworkCondition,
discoveredLocalRelays,
lowbandwidthRelays,
{ inboxRelays, outboxRelays }
);
// Filter out blocked relays and deduplicate final sets
const finalRelaySet = {
inboxRelays: deduplicateRelayUrls(networkOptimizedRelaySet.inboxRelays.filter(r => !blockedRelays.includes(r))),
outboxRelays: deduplicateRelayUrls(networkOptimizedRelaySet.outboxRelays.filter(r => !blockedRelays.includes(r)))
};
// If no relays are working, use anonymous relays as fallback
if (finalRelaySet.inboxRelays.length === 0 && finalRelaySet.outboxRelays.length === 0) {
return {
inboxRelays: deduplicateRelayUrls(anonymousRelays),
outboxRelays: deduplicateRelayUrls(anonymousRelays)
};
}
return finalRelaySet;
}

58
src/lib/utils/subscription_search.ts

@ -3,7 +3,7 @@ import { getMatchingTags, getNpubFromNip05 } from "$lib/utils/nostrUtils"; @@ -3,7 +3,7 @@ import { getMatchingTags, getNpubFromNip05 } from "$lib/utils/nostrUtils";
import { nip19 } from "$lib/utils/nostrUtils";
import { NDKRelaySet, NDKEvent } from "@nostr-dev-kit/ndk";
import { searchCache } from "$lib/utils/searchCache";
import { communityRelay, profileRelays } from "$lib/consts";
import { communityRelays, searchRelays } from "$lib/consts";
import { get } from "svelte/store";
import type {
SearchResult,
@ -20,6 +20,12 @@ import { @@ -20,6 +20,12 @@ import {
isEmojiReaction,
} from "./search_utils";
import { TIMEOUTS, SEARCH_LIMITS } from "./search_constants";
import { activeInboxRelays, activeOutboxRelays } from "$lib/ndk";
// Helper function to normalize URLs for comparison
const normalizeUrl = (url: string): string => {
return url.replace(/\/$/, ''); // Remove trailing slash
};
/**
* Search for events by subscription type (d, t, n)
@ -292,23 +298,40 @@ function createPrimaryRelaySet( @@ -292,23 +298,40 @@ function createPrimaryRelaySet(
searchType: SearchSubscriptionType,
ndk: any,
): NDKRelaySet {
// Use the new relay management system
const searchRelays = [...get(activeInboxRelays), ...get(activeOutboxRelays)];
console.debug('subscription_search: Active relay stores:', {
inboxRelays: get(activeInboxRelays),
outboxRelays: get(activeOutboxRelays),
searchRelays
});
// Debug: Log all relays in NDK pool
const poolRelays = Array.from(ndk.pool.relays.values());
console.debug('subscription_search: NDK pool relays:', poolRelays.map((r: any) => r.url));
if (searchType === "n") {
// For profile searches, use profile relays first
const profileRelaySet = Array.from(ndk.pool.relays.values()).filter(
// For profile searches, use search relays first
const profileRelaySet = poolRelays.filter(
(relay: any) =>
profileRelays.some(
(profileRelay) =>
relay.url === profileRelay || relay.url === profileRelay + "/",
searchRelays.some(
(searchRelay: string) =>
normalizeUrl(relay.url) === normalizeUrl(searchRelay),
),
);
console.debug('subscription_search: Profile relay set:', profileRelaySet.map((r: any) => r.url));
return new NDKRelaySet(new Set(profileRelaySet) as any, ndk);
} else {
// For other searches, use community relay first
const communityRelaySet = Array.from(ndk.pool.relays.values()).filter(
// For other searches, use active relays first
const activeRelaySet = poolRelays.filter(
(relay: any) =>
relay.url === communityRelay || relay.url === communityRelay + "/",
searchRelays.some(
(searchRelay: string) =>
normalizeUrl(relay.url) === normalizeUrl(searchRelay),
),
);
return new NDKRelaySet(new Set(communityRelaySet) as any, ndk);
console.debug('subscription_search: Active relay set:', activeRelaySet.map((r: any) => r.url));
return new NDKRelaySet(new Set(activeRelaySet) as any, ndk);
}
}
@ -511,15 +534,16 @@ async function searchOtherRelaysInBackground( @@ -511,15 +534,16 @@ async function searchOtherRelaysInBackground(
new Set(
Array.from(ndk.pool.relays.values()).filter((relay: any) => {
if (searchType === "n") {
// For profile searches, exclude profile relays from fallback search
return !profileRelays.some(
(profileRelay) =>
relay.url === profileRelay || relay.url === profileRelay + "/",
// For profile searches, exclude search relays from fallback search
return !searchRelays.some(
(searchRelay: string) =>
normalizeUrl(relay.url) === normalizeUrl(searchRelay),
);
} else {
// For other searches, exclude community relay from fallback search
return (
relay.url !== communityRelay && relay.url !== communityRelay + "/"
// For other searches, exclude community relays from fallback search
return !communityRelays.some(
(communityRelay: string) =>
normalizeUrl(relay.url) === normalizeUrl(communityRelay),
);
}
}),

4
src/routes/+layout.svelte

@ -5,6 +5,7 @@ @@ -5,6 +5,7 @@
import { page } from "$app/stores";
import { Alert } from "flowbite-svelte";
import { HammerSolid } from "flowbite-svelte-icons";
import { logCurrentRelayConfiguration } from "$lib/ndk";
// Get standard metadata for OpenGraph tags
let title = "Library of Alexandria";
@ -18,6 +19,9 @@ @@ -18,6 +19,9 @@
onMount(() => {
const rect = document.body.getBoundingClientRect();
// document.body.style.height = `${rect.height}px`;
// Log relay configuration when layout mounts
logCurrentRelayConfiguration();
});
</script>

9
src/routes/+layout.ts

@ -1,5 +1,3 @@ @@ -1,5 +1,3 @@
import { feedTypeStorageKey } from "$lib/consts";
import { FeedType } from "$lib/consts";
import { getPersistedLogin, initNdk, ndkInstance } from "$lib/ndk";
import {
loginWithExtension,
@ -8,18 +6,13 @@ import { @@ -8,18 +6,13 @@ import {
} from "$lib/stores/userStore";
import { loginMethodStorageKey } from "$lib/stores/userStore";
import Pharos, { pharosInstance } from "$lib/parser";
import { feedType } from "$lib/stores";
import type { LayoutLoad } from "./$types";
import { get } from "svelte/store";
export const ssr = false;
export const load: LayoutLoad = () => {
const initialFeedType =
(localStorage.getItem(feedTypeStorageKey) as FeedType) ??
FeedType.StandardRelays;
feedType.set(initialFeedType);
// Initialize NDK with new relay management system
const ndk = initNdk();
ndkInstance.set(ndk);

22
src/routes/+page.svelte

@ -1,14 +1,17 @@ @@ -1,14 +1,17 @@
<script lang="ts">
import { standardRelays, fallbackRelays } from "$lib/consts";
import { Alert, Input } from "flowbite-svelte";
import { HammerSolid } from "flowbite-svelte-icons";
import { userStore } from "$lib/stores/userStore";
import { inboxRelays, ndkSignedIn } from "$lib/ndk";
import { activeInboxRelays, ndkSignedIn } from "$lib/ndk";
import PublicationFeed from "$lib/components/publications/PublicationFeed.svelte";
let searchQuery = $state("");
let user = $state($userStore);
userStore.subscribe((val) => (user = val));
let user = $derived($userStore);
let eventCount = $state({ displayed: 0, total: 0 });
function handleEventCountUpdate(counts: { displayed: number; total: number }) {
eventCount = counts;
}
</script>
<Alert
@ -33,10 +36,15 @@ @@ -33,10 +36,15 @@
class="flex-grow max-w-2xl min-w-[300px] text-base"
/>
</div>
{#if eventCount.total > 0}
<div class="text-center text-sm text-gray-600 dark:text-gray-400">
Showing {eventCount.displayed} of {eventCount.total} events.
</div>
{/if}
<PublicationFeed
relays={standardRelays}
{fallbackRelays}
{searchQuery}
userRelays={$ndkSignedIn ? $inboxRelays : []}
onEventCountUpdate={handleEventCountUpdate}
/>
</main>

17
src/routes/contact/+page.svelte

@ -9,9 +9,9 @@ @@ -9,9 +9,9 @@
Input,
Modal,
} from "flowbite-svelte";
import { ndkInstance, ndkSignedIn } from "$lib/ndk";
import { ndkInstance, ndkSignedIn, activeInboxRelays, activeOutboxRelays } from "$lib/ndk";
import { userStore } from "$lib/stores/userStore";
import { standardRelays } from "$lib/consts";
import { communityRelays } from "$lib/consts";
import type NDK from "@nostr-dev-kit/ndk";
import { NDKEvent, NDKRelaySet } from "@nostr-dev-kit/ndk";
// @ts-ignore - Workaround for Svelte component import issue
@ -62,12 +62,13 @@ @@ -62,12 +62,13 @@
const repoAddress =
"naddr1qvzqqqrhnypzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqy88wumn8ghj7mn0wvhxcmmv9uqq5stvv4uxzmnywf5kz2elajr";
// Hard-coded relays to ensure we have working relays
// Use the new relay management system instead of hardcoded relays
const allRelays = [
"wss://relay.damus.io",
"wss://relay.nostr.band",
"wss://nos.lol",
...standardRelays,
...$activeInboxRelays,
...$activeOutboxRelays,
];
// Hard-coded repository owner pubkey and ID from the task
@ -451,7 +452,7 @@ Also renders nostr identifiers: npubs, nprofiles, nevents, notes, and naddrs. Wi @@ -451,7 +452,7 @@ Also renders nostr identifiers: npubs, nprofiles, nevents, notes, and naddrs. Wi
size="xs"
class="absolute bottom-2 right-2 z-10 opacity-60 hover:opacity-100"
color="light"
on:click={toggleSize}
onclick={toggleSize}
>
{isExpanded ? "⌃" : "⌄"}
</Button>
@ -459,7 +460,7 @@ Also renders nostr identifiers: npubs, nprofiles, nevents, notes, and naddrs. Wi @@ -459,7 +460,7 @@ Also renders nostr identifiers: npubs, nprofiles, nevents, notes, and naddrs. Wi
</div>
<div class="flex justify-end space-x-4">
<Button type="button" color="alternative" on:click={clearForm}>
<Button type="button" color="alternative" onclick={clearForm}>
Clear Form
</Button>
<Button type="submit" tabindex={0}>
@ -586,8 +587,8 @@ Also renders nostr identifiers: npubs, nprofiles, nevents, notes, and naddrs. Wi @@ -586,8 +587,8 @@ Also renders nostr identifiers: npubs, nprofiles, nevents, notes, and naddrs. Wi
Would you like to submit the issue?
</h3>
<div class="flex justify-center gap-4">
<Button color="alternative" on:click={cancelSubmit}>Cancel</Button>
<Button color="primary" on:click={confirmSubmit}>Submit</Button>
<Button color="alternative" onclick={cancelSubmit}>Cancel</Button>
<Button color="primary" onclick={confirmSubmit}>Submit</Button>
</div>
</div>
</Modal>

30
src/routes/events/+page.svelte

@ -13,13 +13,9 @@ @@ -13,13 +13,9 @@
import { getMatchingTags, toNpub } from "$lib/utils/nostrUtils";
import EventInput from "$lib/components/EventInput.svelte";
import { userPubkey, isLoggedIn } from "$lib/stores/authStore.Svelte";
import {
testAllRelays,
logRelayDiagnostics,
} from "$lib/utils/relayDiagnostics";
import CopyToClipboard from "$lib/components/util/CopyToClipboard.svelte";
import { neventEncode, naddrEncode } from "$lib/utils";
import { standardRelays } from "$lib/consts";
import { activeInboxRelays, activeOutboxRelays, logCurrentRelayConfiguration } from "$lib/ndk";
import { getEventType } from "$lib/utils/mime";
import ViewPublicationLink from "$lib/components/util/ViewPublicationLink.svelte";
import { checkCommunity } from "$lib/utils/search_utility";
@ -246,8 +242,15 @@ @@ -246,8 +242,15 @@
return "Reference";
}
function getNeventAddress(event: NDKEvent): string {
return neventEncode(event, standardRelays);
function getNeventUrl(event: NDKEvent): string {
if (event.kind === 0) {
return neventEncode(event, $activeInboxRelays);
}
return neventEncode(event, $activeInboxRelays);
}
function getNaddrUrl(event: NDKEvent): string {
return naddrEncode(event, $activeInboxRelays);
}
function isAddressableEvent(event: NDKEvent): boolean {
@ -259,7 +262,7 @@ @@ -259,7 +262,7 @@
return null;
}
try {
return naddrEncode(event, standardRelays);
return naddrEncode(event, $activeInboxRelays);
} catch {
return null;
}
@ -498,12 +501,11 @@ @@ -498,12 +501,11 @@
handleUrlChange();
});
// Log relay configuration when page mounts
onMount(() => {
userRelayPreference = localStorage.getItem("useUserRelays") === "true";
// Run relay diagnostics to help identify connection issues
testAllRelays().then(logRelayDiagnostics).catch(console.error);
logCurrentRelayConfiguration();
});
</script>
<div class="w-full flex justify-center">
@ -942,8 +944,8 @@ @@ -942,8 +944,8 @@
{#if event.kind !== 0}
<div class="flex flex-col gap-2 mb-4 break-all">
<CopyToClipboard
displayText={shortenAddress(getNeventAddress(event))}
copyText={getNeventAddress(event)}
displayText={shortenAddress(getNeventUrl(event))}
copyText={getNeventUrl(event)}
/>
{#if isAddressableEvent(event)}
{@const naddrAddress = getViewPublicationNaddr(event)}

5
src/routes/publication/+page.ts

@ -2,7 +2,7 @@ import { error } from "@sveltejs/kit"; @@ -2,7 +2,7 @@ import { error } from "@sveltejs/kit";
import type { Load } from "@sveltejs/kit";
import type { NDKEvent } from "@nostr-dev-kit/ndk";
import { nip19 } from "nostr-tools";
import { getActiveRelays } from "$lib/ndk";
import { getActiveRelaySetAsNDKRelaySet } from "$lib/ndk";
import { getMatchingTags } from "$lib/utils/nostrUtils";
/**
@ -68,10 +68,11 @@ async function fetchEventById(ndk: any, id: string): Promise<NDKEvent> { @@ -68,10 +68,11 @@ async function fetchEventById(ndk: any, id: string): Promise<NDKEvent> {
*/
async function fetchEventByDTag(ndk: any, dTag: string): Promise<NDKEvent> {
try {
const relaySet = await getActiveRelaySetAsNDKRelaySet(ndk, true); // true for inbox relays
const event = await ndk.fetchEvent(
{ "#d": [dTag] },
{ closeOnEose: false },
getActiveRelays(ndk),
relaySet,
);
if (!event) {

Loading…
Cancel
Save