Add Tor hidden service support and fallback relay profile fetching (v0.46.0)
Some checks failed
Go / build-and-release (push) Has been cancelled
Some checks failed
Go / build-and-release (push) Has been cancelled
- Add pkg/tor package for Tor hidden service integration - Add Tor config options: ORLY_TOR_ENABLED, ORLY_TOR_PORT, ORLY_TOR_HS_DIR, ORLY_TOR_ONION_ADDRESS - Extend NIP-11 relay info with addresses field for .onion URLs - Add fallback relays (Damus, nos.lol, nostr.band, purplepag.es) for profile lookups - Refactor profile fetching to try local relay first, then fallback relays - Add Tor setup documentation and deployment scripts Files modified: - app/config/config.go: Add Tor configuration options - app/handle-relayinfo.go: Add ExtendedRelayInfo with addresses field - app/main.go: Initialize and manage Tor service lifecycle - app/server.go: Add torService field to Server struct - app/web/src/constants.js: Add FALLBACK_RELAYS - app/web/src/nostr.js: Add fallback relay profile fetching - pkg/tor/: New package for Tor hidden service management - docs/TOR_SETUP.md: Documentation for Tor configuration - deploy/orly-tor.service: Systemd service for Tor integration - scripts/tor-*.sh: Setup scripts for Tor development and production 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,14 @@ export const DEFAULT_RELAYS = [
|
||||
`${wsProtocol}//${window.location.host}/`,
|
||||
];
|
||||
|
||||
// Fallback relays for profile lookups when local relay doesn't have the data
|
||||
export const FALLBACK_RELAYS = [
|
||||
'wss://relay.damus.io',
|
||||
'wss://nos.lol',
|
||||
'wss://relay.nostr.band',
|
||||
'wss://purplepag.es',
|
||||
];
|
||||
|
||||
// Replaceable kinds for the recovery dropdown
|
||||
// Based on NIP-01: kinds 0, 3, and 10000-19999 are replaceable
|
||||
// kinds 30000-39999 are addressable (parameterized replaceable)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { SimplePool } from 'nostr-tools/pool';
|
||||
import { EventStore } from 'applesauce-core';
|
||||
import { PrivateKeySigner } from 'applesauce-signers';
|
||||
import { DEFAULT_RELAYS } from "./constants.js";
|
||||
import { DEFAULT_RELAYS, FALLBACK_RELAYS } from "./constants.js";
|
||||
|
||||
// Nostr client wrapper using nostr-tools
|
||||
class NostrClient {
|
||||
@@ -450,65 +450,115 @@ export async function fetchUserProfile(pubkey) {
|
||||
console.warn("Failed to load cached profile", e);
|
||||
}
|
||||
|
||||
// 2) Fetch profile from relays
|
||||
const filters = [{
|
||||
kinds: [0],
|
||||
authors: [pubkey],
|
||||
limit: 1
|
||||
}];
|
||||
|
||||
// 2) Fetch profile from local relay first
|
||||
try {
|
||||
const filters = [{
|
||||
kinds: [0],
|
||||
authors: [pubkey],
|
||||
limit: 1
|
||||
}];
|
||||
|
||||
const events = await fetchEvents(filters, { timeout: 10000 });
|
||||
|
||||
|
||||
if (events.length > 0) {
|
||||
const profileEvent = events[0];
|
||||
console.log("Profile fetched:", profileEvent);
|
||||
|
||||
// Cache the event
|
||||
await putEvent(profileEvent);
|
||||
|
||||
// Publish the profile event to the local relay
|
||||
try {
|
||||
console.log("Publishing profile event to local relay:", profileEvent.id);
|
||||
await nostrClient.publish(profileEvent);
|
||||
console.log("Profile event successfully saved to local relay");
|
||||
} catch (publishError) {
|
||||
console.warn("Failed to publish profile to local relay:", publishError);
|
||||
// Don't fail the whole operation if publishing fails
|
||||
}
|
||||
|
||||
// Parse profile data
|
||||
const profile = parseProfileFromEvent(profileEvent);
|
||||
|
||||
// Notify listeners that an updated profile is available
|
||||
try {
|
||||
if (typeof window !== "undefined" && window.dispatchEvent) {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("profile-updated", {
|
||||
detail: { pubkey, profile, event: profileEvent },
|
||||
}),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("Failed to dispatch profile-updated event", e);
|
||||
}
|
||||
|
||||
return profile;
|
||||
} else {
|
||||
// No profile found - create a default profile for new users
|
||||
console.log("No profile found for pubkey, creating default:", pubkey);
|
||||
return await createDefaultProfile(pubkey);
|
||||
console.log("Profile fetched from local relay:", profileEvent);
|
||||
return processProfileEvent(profileEvent, pubkey);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch profile:", error);
|
||||
// Try to create default profile on error too
|
||||
try {
|
||||
return await createDefaultProfile(pubkey);
|
||||
} catch (e) {
|
||||
console.error("Failed to create default profile:", e);
|
||||
return null;
|
||||
}
|
||||
console.warn("Failed to fetch profile from local relay:", error);
|
||||
}
|
||||
|
||||
// 3) Try fallback relays if local relay doesn't have the profile
|
||||
console.log("Profile not found on local relay, trying fallback relays:", FALLBACK_RELAYS);
|
||||
try {
|
||||
const profileEvent = await fetchProfileFromFallbackRelays(pubkey, filters);
|
||||
if (profileEvent) {
|
||||
return processProfileEvent(profileEvent, pubkey);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("Failed to fetch profile from fallback relays:", error);
|
||||
}
|
||||
|
||||
// 4) No profile found anywhere - create a default profile for new users
|
||||
console.log("No profile found for pubkey, creating default:", pubkey);
|
||||
try {
|
||||
return await createDefaultProfile(pubkey);
|
||||
} catch (e) {
|
||||
console.error("Failed to create default profile:", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to fetch profile from fallback relays
|
||||
async function fetchProfileFromFallbackRelays(pubkey, filters) {
|
||||
return new Promise((resolve) => {
|
||||
const events = [];
|
||||
const timeoutId = setTimeout(() => {
|
||||
sub.close();
|
||||
// Return the most recent profile event
|
||||
if (events.length > 0) {
|
||||
events.sort((a, b) => b.created_at - a.created_at);
|
||||
resolve(events[0]);
|
||||
} else {
|
||||
resolve(null);
|
||||
}
|
||||
}, 5000);
|
||||
|
||||
const sub = nostrClient.pool.subscribeMany(
|
||||
FALLBACK_RELAYS,
|
||||
filters,
|
||||
{
|
||||
onevent(event) {
|
||||
console.log("Profile event received from fallback relay:", event.id?.substring(0, 8));
|
||||
events.push(event);
|
||||
},
|
||||
oneose() {
|
||||
clearTimeout(timeoutId);
|
||||
sub.close();
|
||||
if (events.length > 0) {
|
||||
events.sort((a, b) => b.created_at - a.created_at);
|
||||
resolve(events[0]);
|
||||
} else {
|
||||
resolve(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Helper to process and cache a profile event
|
||||
async function processProfileEvent(profileEvent, pubkey) {
|
||||
// Cache the event
|
||||
await putEvent(profileEvent);
|
||||
|
||||
// Publish the profile event to the local relay
|
||||
try {
|
||||
console.log("Publishing profile event to local relay:", profileEvent.id);
|
||||
await nostrClient.publish(profileEvent);
|
||||
console.log("Profile event successfully saved to local relay");
|
||||
} catch (publishError) {
|
||||
console.warn("Failed to publish profile to local relay:", publishError);
|
||||
}
|
||||
|
||||
// Parse profile data
|
||||
const profile = parseProfileFromEvent(profileEvent);
|
||||
|
||||
// Notify listeners that an updated profile is available
|
||||
try {
|
||||
if (typeof window !== "undefined" && window.dispatchEvent) {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("profile-updated", {
|
||||
detail: { pubkey, profile, event: profileEvent },
|
||||
}),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("Failed to dispatch profile-updated event", e);
|
||||
}
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user