handle-relayinfo.go raw
1 package app
2
3 import (
4 "encoding/json"
5 "net/http"
6 "sort"
7 "strings"
8
9 "next.orly.dev/pkg/lol/chk"
10 "next.orly.dev/pkg/lol/log"
11 "next.orly.dev/pkg/acl"
12 "next.orly.dev/pkg/nostr/interfaces/signer/p8k"
13 "next.orly.dev/pkg/nostr/encoders/hex"
14 "next.orly.dev/pkg/nostr/relayinfo"
15 "next.orly.dev/pkg/version"
16 )
17
18 // GraphQueryConfig describes graph query capabilities for NIP-11 advertisement.
19 type GraphQueryConfig struct {
20 Enabled bool `json:"enabled"`
21 MaxDepth int `json:"max_depth"`
22 MaxResults int `json:"max_results"`
23 Methods []string `json:"methods"`
24 }
25
26 // ExtendedRelayInfo extends the standard NIP-11 relay info with additional fields.
27 // The Addresses field contains alternative WebSocket URLs for the relay (e.g., .onion).
28 type ExtendedRelayInfo struct {
29 *relayinfo.T
30 Addresses []string `json:"addresses,omitempty"`
31 GraphQuery *GraphQueryConfig `json:"graph_query,omitempty"`
32 Theme string `json:"theme,omitempty"`
33 BlossomEnabled bool `json:"blossom_enabled,omitempty"`
34 DBType string `json:"db_type,omitempty"`
35 }
36
37 // HandleRelayInfo generates and returns a relay information document in JSON
38 // format based on the server's configuration and supported NIPs.
39 //
40 // # Parameters
41 //
42 // - w: HTTP response writer used to send the generated document.
43 //
44 // - r: HTTP request object containing incoming client request data.
45 //
46 // # Expected Behaviour
47 //
48 // The function constructs a relay information document using either the
49 // Informer interface implementation or predefined server configuration. It
50 // returns this document as a JSON response to the client.
51 func (s *Server) HandleRelayInfo(w http.ResponseWriter, r *http.Request) {
52 w.Header().Set("Content-Type", "application/json")
53 w.Header().Set("Vary", "Accept")
54 log.D.Ln("handling relay information document")
55 var info *relayinfo.T
56 nips := []relayinfo.NIP{
57 relayinfo.BasicProtocol,
58 relayinfo.Authentication,
59 relayinfo.EncryptedDirectMessage,
60 relayinfo.EventDeletion,
61 relayinfo.RelayInformationDocument,
62 relayinfo.GenericTagQueries,
63 // relayinfo.NostrMarketplace,
64 relayinfo.CountingResults,
65 relayinfo.EventTreatment,
66 relayinfo.CommandResults,
67 relayinfo.ParameterizedReplaceableEvents,
68 relayinfo.ExpirationTimestamp,
69 relayinfo.ProtectedEvents,
70 relayinfo.RelayListMetadata,
71 relayinfo.SearchCapability,
72 }
73 // Add NIP-43 if enabled
74 if s.Config.NIP43Enabled {
75 nips = append(nips, relayinfo.RelayAccessMetadata)
76 }
77 // Add NIP-77 (negentropy) if enabled
78 if s.Config.NegentropyEnabled {
79 nips = append(nips, relayinfo.NIP{Number: 77, Description: "Negentropy-based sync"})
80 }
81 // Add NIP-86 (Relay Management API) if ACL mode supports it
82 if s.Config.ACLMode == "managed" || s.Config.ACLMode == "curating" {
83 nips = append(nips, relayinfo.NIP{Number: 86, Description: "Relay Management API"})
84 }
85 supportedNIPs := relayinfo.GetList(nips...)
86 if s.Config.ACLMode != "none" {
87 nipsACL := []relayinfo.NIP{
88 relayinfo.BasicProtocol,
89 relayinfo.Authentication,
90 relayinfo.EncryptedDirectMessage,
91 relayinfo.EventDeletion,
92 relayinfo.RelayInformationDocument,
93 relayinfo.GenericTagQueries,
94 // relayinfo.NostrMarketplace,
95 relayinfo.CountingResults,
96 relayinfo.EventTreatment,
97 relayinfo.CommandResults,
98 relayinfo.ParameterizedReplaceableEvents,
99 relayinfo.ExpirationTimestamp,
100 relayinfo.ProtectedEvents,
101 relayinfo.RelayListMetadata,
102 relayinfo.SearchCapability,
103 }
104 // Add NIP-43 if enabled
105 if s.Config.NIP43Enabled {
106 nipsACL = append(nipsACL, relayinfo.RelayAccessMetadata)
107 }
108 // Add NIP-77 (negentropy) if enabled
109 if s.Config.NegentropyEnabled {
110 nipsACL = append(nipsACL, relayinfo.NIP{Number: 77, Description: "Negentropy-based sync"})
111 }
112 // Add NIP-86 (Relay Management API) if ACL mode supports it
113 if s.Config.ACLMode == "managed" || s.Config.ACLMode == "curating" {
114 nipsACL = append(nipsACL, relayinfo.NIP{Number: 86, Description: "Relay Management API"})
115 }
116 supportedNIPs = relayinfo.GetList(nipsACL...)
117 }
118 sort.Sort(supportedNIPs)
119 log.I.Ln("supported NIPs", supportedNIPs)
120 // Get relay identity pubkey as hex
121 var relayPubkey string
122 if skb, err := s.DB.GetRelayIdentitySecret(); err == nil && len(skb) == 32 {
123 var sign *p8k.Signer
124 var sigErr error
125 if sign, sigErr = p8k.New(); sigErr == nil {
126 if err := sign.InitSec(skb); err == nil {
127 relayPubkey = hex.Enc(sign.Pub())
128 }
129 }
130 }
131
132 // Default relay info
133 name := s.Config.AppName
134 description := version.Description + " dashboard: " + s.DashboardURL(r)
135 icon := "https://i.nostr.build/6wGXAn7Zaw9mHxFg.png"
136
137 // Override with branding config if available
138 if s.brandingMgr != nil {
139 nip11 := s.brandingMgr.NIP11Config()
140 if nip11.Name != "" {
141 name = nip11.Name
142 }
143 if nip11.Description != "" {
144 description = nip11.Description
145 }
146 if nip11.Icon != "" {
147 icon = nip11.Icon
148 }
149 }
150
151 // Override with managed ACL config if in managed mode
152 if s.Config.ACLMode == "managed" {
153 // Get managed ACL instance
154 for _, aclInstance := range acl.Registry.ACLs() {
155 if aclInstance.Type() == "managed" {
156 if managed, ok := aclInstance.(*acl.Managed); ok {
157 managedACL := managed.GetManagedACL()
158 if managedACL != nil {
159 if config, err := managedACL.GetRelayConfig(); err == nil {
160 if config.RelayName != "" {
161 name = config.RelayName
162 }
163 if config.RelayDescription != "" {
164 description = config.RelayDescription
165 }
166 if config.RelayIcon != "" {
167 icon = config.RelayIcon
168 }
169 }
170 }
171 }
172 break
173 }
174 }
175 }
176
177 // Restricted writes applies when ACL mode is not managed/curating but also not none
178 // (e.g., follows mode restricts writes to followed pubkeys)
179 restrictedWrites := s.Config.ACLMode != "managed" && s.Config.ACLMode != "curating" && s.Config.ACLMode != "none"
180
181 info = &relayinfo.T{
182 Name: name,
183 Description: description,
184 PubKey: relayPubkey,
185 Nips: supportedNIPs,
186 Software: version.URL,
187 Version: strings.TrimPrefix(version.V, "v"),
188 Limitation: relayinfo.Limits{
189 AuthRequired: s.Config.AuthRequired || s.Config.ACLMode != "none",
190 RestrictedWrites: restrictedWrites,
191 PaymentRequired: s.Config.MonthlyPriceSats > 0,
192 },
193 Icon: icon,
194 }
195
196 // Build addresses list from config and Tor service
197 var addresses []string
198
199 // Add configured relay addresses
200 if len(s.Config.RelayAddresses) > 0 {
201 addresses = append(addresses, s.Config.RelayAddresses...)
202 }
203
204 // Add addresses from all transports (Tor .onion, etc.)
205 if s.transportMgr != nil {
206 addresses = append(addresses, s.transportMgr.Addresses()...)
207 }
208
209 // Build graph query config if enabled
210 var graphConfig *GraphQueryConfig
211 if s.graphExecutor != nil && s.Config.GraphQueriesEnabled {
212 graphEnabled, maxDepth, maxResults, _ := s.Config.GetGraphConfigValues()
213 if graphEnabled {
214 graphConfig = &GraphQueryConfig{
215 Enabled: true,
216 MaxDepth: maxDepth,
217 MaxResults: maxResults,
218 Methods: []string{"follows", "followers", "mentions", "thread"},
219 }
220 }
221 }
222
223 // Return extended info if we have addresses, graph query support, custom theme, or blossom
224 theme := s.Config.Theme
225 if theme != "auto" && theme != "light" && theme != "dark" {
226 theme = "auto"
227 }
228 // Blossom is only available if the server is actually initialized (requires Badger backend)
229 blossomEnabled := s.blossomServer != nil
230 dbType := s.Config.DBType
231 if len(addresses) > 0 || graphConfig != nil || theme != "auto" || blossomEnabled || dbType != "badger" {
232 extInfo := &ExtendedRelayInfo{
233 T: info,
234 Addresses: addresses,
235 GraphQuery: graphConfig,
236 Theme: theme,
237 BlossomEnabled: blossomEnabled,
238 DBType: dbType,
239 }
240 if err := json.NewEncoder(w).Encode(extInfo); chk.E(err) {
241 }
242 } else {
243 if err := json.NewEncoder(w).Encode(info); chk.E(err) {
244 }
245 }
246 }
247