Browse Source

fixed relay connections

master
silberengel 7 months ago
parent
commit
43a7dcd14c
  1. 9
      src/lib/consts.ts
  2. 82
      src/lib/ndk.ts
  3. 4
      src/lib/stores/userStore.ts
  4. 223
      src/lib/utils/relay_management.ts

9
src/lib/consts.ts

@ -32,9 +32,7 @@ export const secondaryRelays = [ @@ -32,9 +32,7 @@ export const secondaryRelays = [
export const anonymousRelays = [
"wss://freelay.sovbit.host",
"wss://thecitadel.nostr1.com",
"wss://relay.damus.io",
"wss://relay.nostr.band"
"wss://thecitadel.nostr1.com"
];
export const lowbandwidthRelays = [
@ -44,8 +42,9 @@ export const lowbandwidthRelays = [ @@ -44,8 +42,9 @@ export const lowbandwidthRelays = [
];
export const localRelays: string[] = [
"wss://localhost:8080",
"wss://localhost:4869",
"ws://localhost:8080",
"ws://localhost:4869",
"ws://localhost:3334"
];
export enum FeedType {

82
src/lib/ndk.ts

@ -9,6 +9,7 @@ import NDK, { @@ -9,6 +9,7 @@ import NDK, {
import { writable, get, type Writable } from "svelte/store";
import {
loginStorageKey,
anonymousRelays,
} from "./consts.ts";
import {
buildCompleteRelaySet,
@ -91,8 +92,18 @@ function clearPersistentRelaySet(): void { @@ -91,8 +92,18 @@ function clearPersistentRelaySet(): void {
}
// Subscribe to userStore changes and update ndkSignedIn accordingly
userStore.subscribe((userState) => {
userStore.subscribe(async (userState) => {
ndkSignedIn.set(userState.signedIn);
// Refresh relay stores when user state changes
const ndk = get(ndkInstance);
if (ndk) {
try {
await refreshRelayStores(ndk);
} catch (error) {
console.warn('[NDK.ts] Failed to refresh relay stores on user state change:', error);
}
}
});
/**
@ -322,15 +333,21 @@ export function clearPersistedRelays(user: NDKUser): void { @@ -322,15 +333,21 @@ export function clearPersistedRelays(user: NDKUser): void {
/**
* Ensures a relay URL uses secure WebSocket protocol
* @param url The relay URL to secure
* @returns The URL with wss:// protocol
* @returns The URL with appropriate protocol (ws:// for localhost, wss:// for remote)
*/
function ensureSecureWebSocket(url: string): string {
// Replace ws:// with wss:// if present
// 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(
`[NDK.ts] Protocol downgrade detected: ${url} -> ${secureUrl}`,
`[NDK.ts] Protocol upgrade for remote relay: ${url} -> ${secureUrl}`,
);
}
@ -341,9 +358,10 @@ function ensureSecureWebSocket(url: string): string { @@ -341,9 +358,10 @@ function ensureSecureWebSocket(url: string): string {
* Creates a relay with proper authentication handling
*/
function createRelayWithAuth(url: string, ndk: NDK): NDKRelay {
try {
console.debug(`[NDK.ts] Creating relay with URL: ${url}`);
// Ensure the URL is using wss:// protocol
// Ensure the URL is using appropriate protocol
const secureUrl = ensureSecureWebSocket(url);
// Add connection timeout and error handling
@ -355,32 +373,70 @@ function createRelayWithAuth(url: string, ndk: NDK): NDKRelay { @@ -355,32 +373,70 @@ function createRelayWithAuth(url: string, ndk: NDK): NDKRelay {
// Set up connection timeout
const connectionTimeout = setTimeout(() => {
try {
console.warn(`[NDK.ts] Connection timeout for ${secureUrl}`);
relay.disconnect();
} catch {
// Silently ignore disconnect errors
}
}, 5000); // 5 second timeout
// Set up custom authentication handling only if user is signed in
if (ndk.signer && ndk.activeUser) {
const authPolicy = new CustomRelayAuthPolicy(ndk);
relay.on("connect", () => {
try {
console.debug(`[NDK.ts] Relay connected: ${secureUrl}`);
clearTimeout(connectionTimeout);
authPolicy.authenticate(relay);
} catch {
// Silently handle connect handler errors
}
});
} else {
relay.on("connect", () => {
try {
console.debug(`[NDK.ts] Relay connected: ${secureUrl}`);
clearTimeout(connectionTimeout);
} catch {
// Silently handle connect handler errors
}
});
}
// Add error handling
relay.on("disconnect", () => {
try {
console.debug(`[NDK.ts] Relay disconnected: ${secureUrl}`);
clearTimeout(connectionTimeout);
} catch {
// Silently handle disconnect handler errors
}
});
return relay;
} catch (error) {
// If relay creation fails, try to use an anonymous relay as fallback
console.debug(`[NDK.ts] Failed to create relay for ${url}, trying anonymous relay fallback`);
// Find an anonymous relay that's not the same as the failed URL
const fallbackUrl = anonymousRelays.find(relay => relay !== url) || anonymousRelays[0];
if (fallbackUrl) {
console.debug(`[NDK.ts] Using anonymous relay as fallback: ${fallbackUrl}`);
try {
const fallbackRelay = new NDKRelay(fallbackUrl, NDKRelayAuthPolicies.signIn({ ndk }), ndk);
return fallbackRelay;
} catch (fallbackError) {
console.debug(`[NDK.ts] Fallback relay creation also failed: ${fallbackError}`);
}
}
// If all else fails, create a minimal relay that will fail gracefully
console.debug(`[NDK.ts] All fallback attempts failed, creating minimal relay for ${url}`);
const minimalRelay = new NDKRelay(url, undefined, ndk);
return minimalRelay;
}
}
@ -478,13 +534,27 @@ export function logCurrentRelayConfiguration(): void { @@ -478,13 +534,27 @@ export function logCurrentRelayConfiguration(): void {
console.log(`📊 Total: ${inboxRelays.length} inbox, ${outboxRelays.length} outbox`);
}
/**
* Clears the relay set cache to force a rebuild
*/
export function clearRelaySetCache(): void {
console.debug('[NDK.ts] Clearing relay set cache');
persistentRelaySet = null;
relaySetLastUpdated = 0;
// Clear from localStorage as well
if (typeof localStorage !== 'undefined') {
localStorage.removeItem('alexandria/relay_set_cache');
}
}
/**
* 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);
clearRelaySetCache(); // Clear cache when user state changes
await updateActiveRelayStores(ndk, true); // Force update
}
/**

4
src/lib/stores/userStore.ts

@ -206,7 +206,7 @@ export async function loginWithExtension() { @@ -206,7 +206,7 @@ export async function loginWithExtension() {
// Update relay stores with the new user's relays
try {
console.debug('[userStore.ts] loginWithExtension: Updating relay stores for authenticated user');
await updateActiveRelayStores(ndk);
await updateActiveRelayStores(ndk, true); // Force update to rebuild relay set for authenticated user
} catch (error) {
console.warn('[userStore.ts] loginWithExtension: Failed to update relay stores:', error);
}
@ -273,7 +273,7 @@ export async function loginWithAmber(amberSigner: NDKSigner, user: NDKUser) { @@ -273,7 +273,7 @@ export async function loginWithAmber(amberSigner: NDKSigner, user: NDKUser) {
// Update relay stores with the new user's relays
try {
console.debug('[userStore.ts] loginWithAmber: Updating relay stores for authenticated user');
await updateActiveRelayStores(ndk);
await updateActiveRelayStores(ndk, true); // Force update to rebuild relay set for authenticated user
} catch (error) {
console.warn('[userStore.ts] loginWithAmber: Failed to update relay stores:', error);
}

223
src/lib/utils/relay_management.ts

@ -43,12 +43,12 @@ export function deduplicateRelayUrls(urls: string[]): string[] { @@ -43,12 +43,12 @@ export function deduplicateRelayUrls(urls: string[]): string[] {
}
/**
* Tests connection to a relay and returns connection status
* @param relayUrl The relay URL to test
* Tests connection to a local relay (ws:// protocol)
* @param relayUrl The local relay URL to test (should be ws://)
* @param ndk The NDK instance
* @returns Promise that resolves to connection status
*/
export function testRelayConnection(
export function testLocalRelayConnection(
relayUrl: string,
ndk: NDK,
): Promise<{
@ -58,29 +58,36 @@ export function testRelayConnection( @@ -58,29 +58,36 @@ export function testRelayConnection(
actualUrl?: string;
}> {
return new Promise((resolve) => {
// Ensure the URL is using wss:// protocol
const secureUrl = ensureSecureWebSocket(relayUrl);
try {
// Ensure the URL is using ws:// protocol for local relays
const localUrl = relayUrl.replace(/^wss:\/\//, "ws://");
// Use the existing NDK instance instead of creating a new one
const relay = new NDKRelay(secureUrl, undefined, ndk);
const relay = new NDKRelay(localUrl, undefined, ndk);
let authRequired = false;
let connected = false;
let error: string | undefined;
let actualUrl: string | undefined;
const timeout = setTimeout(() => {
try {
relay.disconnect();
} catch {
// Silently ignore disconnect errors
}
resolve({
connected: false,
requiresAuth: authRequired,
error: "Connection timeout",
actualUrl,
});
}, 3000); // Increased timeout to 3 seconds to give relays more time
}, 3000);
// Wrap all event handlers in try-catch to prevent errors from bubbling up
relay.on("connect", () => {
try {
connected = true;
actualUrl = secureUrl;
actualUrl = localUrl;
clearTimeout(timeout);
relay.disconnect();
resolve({
@ -89,15 +96,30 @@ export function testRelayConnection( @@ -89,15 +96,30 @@ export function testRelayConnection(
error,
actualUrl,
});
} catch {
// Silently handle any errors in connect handler
clearTimeout(timeout);
resolve({
connected: false,
requiresAuth: false,
error: "Connection handler error",
actualUrl: localUrl,
});
}
});
relay.on("notice", (message: string) => {
try {
if (message.includes("auth-required")) {
authRequired = true;
}
} catch {
// Silently ignore notice handler errors
}
});
relay.on("disconnect", () => {
try {
if (!connected) {
error = "Connection failed";
clearTimeout(timeout);
@ -108,36 +130,145 @@ export function testRelayConnection( @@ -108,36 +130,145 @@ export function testRelayConnection(
actualUrl,
});
}
} catch {
// Silently handle any errors in disconnect handler
clearTimeout(timeout);
resolve({
connected: false,
requiresAuth: false,
error: "Disconnect handler error",
actualUrl: localUrl,
});
}
});
// Wrap the connect call in try-catch
try {
relay.connect();
} catch (connectError) {
// Silently handle connection errors
clearTimeout(timeout);
resolve({
connected: false,
requiresAuth: false,
error: "Connection failed",
actualUrl: localUrl,
});
}
} catch (outerError) {
// Catch any other errors that might occur during setup
resolve({
connected: false,
requiresAuth: false,
error: "Setup failed",
actualUrl: relayUrl,
});
}
});
}
/**
* 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)
* Tests connection to a remote relay (wss:// protocol)
* @param relayUrl The remote relay URL to test
* @param ndk The NDK instance
* @returns Promise that resolves to connection status
*/
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://");
}
export function testRemoteRelayConnection(
relayUrl: string,
ndk: NDK,
): Promise<{
connected: boolean;
requiresAuth: boolean;
error?: string;
actualUrl?: string;
}> {
return new Promise((resolve) => {
// Ensure the URL is using wss:// protocol for remote relays
const secureUrl = relayUrl.replace(/^ws:\/\//, "wss://");
// Replace ws:// with wss:// for remote relays
const secureUrl = url.replace(/^ws:\/\//, "wss://");
console.debug(`[relay_management.ts] Testing remote relay connection: ${secureUrl}`);
if (secureUrl !== url) {
console.warn(
`[relay_management.ts] Protocol upgrade for rem ote relay: ${url} -> ${secureUrl}`,
);
// 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(() => {
console.debug(`[relay_management.ts] Relay ${secureUrl} connection timeout`);
relay.disconnect();
resolve({
connected: false,
requiresAuth: authRequired,
error: "Connection timeout",
actualUrl,
});
}, 3000);
relay.on("connect", () => {
console.debug(`[relay_management.ts] Relay ${secureUrl} connected successfully`);
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) {
console.debug(`[relay_management.ts] Relay ${secureUrl} disconnected without connecting`);
error = "Connection failed";
clearTimeout(timeout);
resolve({
connected: false,
requiresAuth: authRequired,
error,
actualUrl,
});
}
});
relay.connect();
});
}
return secureUrl;
/**
* 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 function testRelayConnection(
relayUrl: string,
ndk: NDK,
): Promise<{
connected: boolean;
requiresAuth: boolean;
error?: string;
actualUrl?: string;
}> {
// Determine if this is a local or remote relay
if (relayUrl.includes('localhost') || relayUrl.includes('127.0.0.1')) {
return testLocalRelayConnection(relayUrl, ndk);
} else {
return testRemoteRelayConnection(relayUrl, ndk);
}
}
/**
* Tests connection to local relays
* @param localRelayUrls Array of local relay URLs to test
@ -145,33 +276,38 @@ function ensureSecureWebSocket(url: string): string { @@ -145,33 +276,38 @@ function ensureSecureWebSocket(url: string): string {
* @returns Promise that resolves to array of working local relay URLs
*/
async function testLocalRelays(localRelayUrls: string[], ndk: NDK): Promise<string[]> {
try {
const workingRelays: string[] = [];
if (localRelayUrls.length === 0) {
return workingRelays;
}
console.debug(`[relay_management.ts] Testing ${localRelayUrls.length} local relays...`);
// Test local relays quietly, without logging failures
await Promise.all(
localRelayUrls.map(async (url) => {
try {
const result = await testRelayConnection(url, ndk);
const result = await testLocalRelayConnection(url, ndk);
if (result.connected) {
workingRelays.push(url);
console.debug(`[relay_management.ts] Local relay connected: ${url}`);
} else {
console.debug(`[relay_management.ts] Local relay failed: ${url} - ${result.error}`);
}
// Don't log failures - local relays are optional
} catch {
// Silently ignore local relay failures - they're optional
console.debug(`[relay_management.ts] Local relay error (ignored): ${url}`);
}
})
);
console.debug(`[relay_management.ts] Found ${workingRelays.length} working local relays`);
if (workingRelays.length > 0) {
console.info(`[relay_management.ts] Found ${workingRelays.length} working local relays`);
}
return workingRelays;
} catch {
// If anything goes wrong with the entire local relay testing process,
// just return an empty array silently
return [];
}
}
/**
@ -391,12 +527,18 @@ async function testRelaySet(relayUrls: string[], ndk: NDK): Promise<string[]> { @@ -391,12 +527,18 @@ async function testRelaySet(relayUrls: string[], ndk: NDK): Promise<string[]> {
const workingRelays: string[] = [];
const maxConcurrent = 2; // Reduce to 2 relays at a time to avoid overwhelming them
console.debug(`[relay_management.ts] Testing ${relayUrls.length} relays in batches of ${maxConcurrent}`);
console.debug(`[relay_management.ts] Relay URLs to test:`, relayUrls);
for (let i = 0; i < relayUrls.length; i += maxConcurrent) {
const batch = relayUrls.slice(i, i + maxConcurrent);
console.debug(`[relay_management.ts] Testing batch ${Math.floor(i/maxConcurrent) + 1}:`, batch);
const batchPromises = batch.map(async (url) => {
try {
console.debug(`[relay_management.ts] Testing relay: ${url}`);
const result = await testRelayConnection(url, ndk);
console.debug(`[relay_management.ts] Relay ${url} test result:`, result);
return result.connected ? url : null;
} catch (error) {
console.debug(`[relay_management.ts] Failed to test relay ${url}:`, error);
@ -409,9 +551,12 @@ async function testRelaySet(relayUrls: string[], ndk: NDK): Promise<string[]> { @@ -409,9 +551,12 @@ async function testRelaySet(relayUrls: string[], ndk: NDK): Promise<string[]> {
.filter((result): result is PromiseFulfilledResult<string | null> => result.status === 'fulfilled')
.map(result => result.value)
.filter((url): url is string => url !== null);
console.debug(`[relay_management.ts] Batch ${Math.floor(i/maxConcurrent) + 1} working relays:`, batchWorkingRelays);
workingRelays.push(...batchWorkingRelays);
}
console.debug(`[relay_management.ts] Total working relays after testing:`, workingRelays);
return workingRelays;
}
@ -496,9 +641,16 @@ export async function buildCompleteRelaySet( @@ -496,9 +641,16 @@ export async function buildCompleteRelaySet(
};
}
// Use tested relays and deduplicate
const inboxRelays = testedInboxRelays.length > 0 ? deduplicateRelayUrls(testedInboxRelays) : deduplicateRelayUrls(secondaryRelays);
const outboxRelays = testedOutboxRelays.length > 0 ? deduplicateRelayUrls(testedOutboxRelays) : deduplicateRelayUrls(secondaryRelays);
// Always include some remote relays as fallback, even when local relays are working
const fallbackRelays = deduplicateRelayUrls([...anonymousRelays, ...secondaryRelays]);
// Use tested relays and add fallback relays
const inboxRelays = testedInboxRelays.length > 0
? deduplicateRelayUrls([...testedInboxRelays, ...fallbackRelays])
: deduplicateRelayUrls(fallbackRelays);
const outboxRelays = testedOutboxRelays.length > 0
? deduplicateRelayUrls([...testedOutboxRelays, ...fallbackRelays])
: deduplicateRelayUrls(fallbackRelays);
// Apply network condition optimization
const currentNetworkCondition = get(networkCondition);
@ -515,8 +667,9 @@ export async function buildCompleteRelaySet( @@ -515,8 +667,9 @@ export async function buildCompleteRelaySet(
outboxRelays: deduplicateRelayUrls(networkOptimizedRelaySet.outboxRelays.filter((r: string) => !blockedRelays.includes(r)))
};
// If no relays are working, use anonymous relays as fallback
// Ensure we always have at least some relays
if (finalRelaySet.inboxRelays.length === 0 && finalRelaySet.outboxRelays.length === 0) {
console.warn('[relay_management.ts] No relays available, using anonymous relays as final fallback');
return {
inboxRelays: deduplicateRelayUrls(anonymousRelays),
outboxRelays: deduplicateRelayUrls(anonymousRelays)

Loading…
Cancel
Save