You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
124 lines
3.1 KiB
124 lines
3.1 KiB
// Package sync provides NIP-11 relay information document fetching and caching |
|
package sync |
|
|
|
import ( |
|
"context" |
|
"crypto/tls" |
|
"encoding/json" |
|
"fmt" |
|
"net/http" |
|
"strings" |
|
"sync" |
|
"time" |
|
|
|
"next.orly.dev/pkg/protocol/relayinfo" |
|
) |
|
|
|
// NIP11Cache caches relay information documents with TTL |
|
type NIP11Cache struct { |
|
cache map[string]*cachedRelayInfo |
|
mutex sync.RWMutex |
|
ttl time.Duration |
|
} |
|
|
|
// cachedRelayInfo holds cached relay info with expiration |
|
type cachedRelayInfo struct { |
|
info *relayinfo.T |
|
expiresAt time.Time |
|
} |
|
|
|
// NewNIP11Cache creates a new NIP-11 cache with the specified TTL |
|
func NewNIP11Cache(ttl time.Duration) *NIP11Cache { |
|
return &NIP11Cache{ |
|
cache: make(map[string]*cachedRelayInfo), |
|
ttl: ttl, |
|
} |
|
} |
|
|
|
// Get fetches relay information for a given URL, using cache if available |
|
func (c *NIP11Cache) Get(ctx context.Context, relayURL string) (*relayinfo.T, error) { |
|
// Normalize URL - remove protocol and trailing slash |
|
normalizedURL := strings.TrimPrefix(relayURL, "https://") |
|
normalizedURL = strings.TrimPrefix(normalizedURL, "http://") |
|
normalizedURL = strings.TrimSuffix(normalizedURL, "/") |
|
|
|
// Check cache first |
|
c.mutex.RLock() |
|
if cached, exists := c.cache[normalizedURL]; exists && time.Now().Before(cached.expiresAt) { |
|
c.mutex.RUnlock() |
|
return cached.info, nil |
|
} |
|
c.mutex.RUnlock() |
|
|
|
// Fetch fresh data |
|
info, err := c.fetchNIP11(ctx, relayURL) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
// Cache the result |
|
c.mutex.Lock() |
|
c.cache[normalizedURL] = &cachedRelayInfo{ |
|
info: info, |
|
expiresAt: time.Now().Add(c.ttl), |
|
} |
|
c.mutex.Unlock() |
|
|
|
return info, nil |
|
} |
|
|
|
// fetchNIP11 fetches relay information document from a given URL |
|
func (c *NIP11Cache) fetchNIP11(ctx context.Context, relayURL string) (*relayinfo.T, error) { |
|
// Construct NIP-11 URL |
|
nip11URL := relayURL |
|
if !strings.HasSuffix(nip11URL, "/") { |
|
nip11URL += "/" |
|
} |
|
nip11URL += ".well-known/nostr.json" |
|
|
|
// Create HTTP client with timeout |
|
client := &http.Client{ |
|
Timeout: 10 * time.Second, |
|
Transport: &http.Transport{ |
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: false}, |
|
}, |
|
} |
|
|
|
req, err := http.NewRequestWithContext(ctx, "GET", nip11URL, nil) |
|
if err != nil { |
|
return nil, fmt.Errorf("failed to create request: %w", err) |
|
} |
|
|
|
req.Header.Set("Accept", "application/nostr+json") |
|
|
|
resp, err := client.Do(req) |
|
if err != nil { |
|
return nil, fmt.Errorf("failed to fetch NIP-11 document from %s: %w", nip11URL, err) |
|
} |
|
defer resp.Body.Close() |
|
|
|
if resp.StatusCode != http.StatusOK { |
|
return nil, fmt.Errorf("NIP-11 request failed with status %d", resp.StatusCode) |
|
} |
|
|
|
var info relayinfo.T |
|
if err := json.NewDecoder(resp.Body).Decode(&info); err != nil { |
|
return nil, fmt.Errorf("failed to decode NIP-11 document: %w", err) |
|
} |
|
|
|
return &info, nil |
|
} |
|
|
|
// GetPubkey fetches the relay's identity pubkey from its NIP-11 document |
|
func (c *NIP11Cache) GetPubkey(ctx context.Context, relayURL string) (string, error) { |
|
info, err := c.Get(ctx, relayURL) |
|
if err != nil { |
|
return "", err |
|
} |
|
|
|
if info.PubKey == "" { |
|
return "", fmt.Errorf("relay %s does not provide pubkey in NIP-11 document", relayURL) |
|
} |
|
|
|
return info.PubKey, nil |
|
}
|
|
|