summaryrefslogtreecommitdiffstats
path: root/internal/handler
diff options
context:
space:
mode:
authorbndw <ben@bdw.to>2026-02-13 20:50:27 -0800
committerbndw <ben@bdw.to>2026-02-13 20:50:27 -0800
commit97e6c9a0c2c32bf514d3a4218d239741f1dc26c8 (patch)
tree259cc3a149c9521adb9d212d0f4e5261a1ed08c6 /internal/handler
parentdfa19ff0776be0850ad7b86ca579601431349593 (diff)
feat: add HTML index page for browser viewing
Add a beautiful HTML landing page when visiting relay in browser: - Shows all three protocol endpoints (gRPC, Connect, WebSocket) - Lists supported NIPs (01, 09, 11) - Displays relay features and info - Responsive design with gradient styling - Serves on GET requests (regular Accept header) - NIP-11 still served for Accept: application/nostr+json
Diffstat (limited to 'internal/handler')
-rw-r--r--internal/handler/websocket/handler.go28
-rw-r--r--internal/handler/websocket/index.go194
2 files changed, 218 insertions, 4 deletions
diff --git a/internal/handler/websocket/handler.go b/internal/handler/websocket/handler.go
index 224a2f8..2d531e9 100644
--- a/internal/handler/websocket/handler.go
+++ b/internal/handler/websocket/handler.go
@@ -21,8 +21,9 @@ type EventStore interface {
21} 21}
22 22
23type Handler struct { 23type Handler struct {
24 store EventStore 24 store EventStore
25 subs *subscription.Manager 25 subs *subscription.Manager
26 indexData IndexData
26} 27}
27 28
28func NewHandler(store EventStore, subs *subscription.Manager) *Handler { 29func NewHandler(store EventStore, subs *subscription.Manager) *Handler {
@@ -32,9 +33,28 @@ func NewHandler(store EventStore, subs *subscription.Manager) *Handler {
32 } 33 }
33} 34}
34 35
36// SetIndexData sets the addresses for the index page
37func (h *Handler) SetIndexData(grpcAddr, httpAddr, wsAddr string) {
38 h.indexData = IndexData{
39 GrpcAddr: grpcAddr,
40 HttpAddr: httpAddr,
41 WsAddr: wsAddr,
42 }
43}
44
35func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 45func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
36 if r.Method == "GET" && r.Header.Get("Accept") == "application/nostr+json" { 46 // Handle GET requests
37 h.ServeNIP11(w, r) 47 if r.Method == "GET" {
48 accept := r.Header.Get("Accept")
49
50 // NIP-11: Relay information document
51 if accept == "application/nostr+json" {
52 h.ServeNIP11(w, r)
53 return
54 }
55
56 // Serve HTML index for browsers
57 h.ServeIndex(w, r, h.indexData)
38 return 58 return
39 } 59 }
40 60
diff --git a/internal/handler/websocket/index.go b/internal/handler/websocket/index.go
new file mode 100644
index 0000000..96bcfd4
--- /dev/null
+++ b/internal/handler/websocket/index.go
@@ -0,0 +1,194 @@
1package websocket
2
3import (
4 "html/template"
5 "net/http"
6)
7
8var indexTemplate = template.Must(template.New("index").Parse(`<!DOCTYPE html>
9<html lang="en">
10<head>
11 <meta charset="UTF-8">
12 <meta name="viewport" content="width=device-width, initial-scale=1.0">
13 <title>Nostr gRPC Relay</title>
14 <style>
15 * {
16 margin: 0;
17 padding: 0;
18 box-sizing: border-box;
19 }
20 body {
21 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
22 line-height: 1.6;
23 color: #333;
24 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
25 min-height: 100vh;
26 padding: 2rem;
27 }
28 .container {
29 max-width: 800px;
30 margin: 0 auto;
31 background: white;
32 border-radius: 12px;
33 box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
34 overflow: hidden;
35 }
36 .header {
37 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
38 color: white;
39 padding: 2rem;
40 text-align: center;
41 }
42 .header h1 {
43 font-size: 2.5rem;
44 margin-bottom: 0.5rem;
45 }
46 .header p {
47 font-size: 1.1rem;
48 opacity: 0.9;
49 }
50 .content {
51 padding: 2rem;
52 }
53 .section {
54 margin-bottom: 2rem;
55 }
56 .section h2 {
57 color: #667eea;
58 margin-bottom: 1rem;
59 font-size: 1.5rem;
60 border-bottom: 2px solid #f0f0f0;
61 padding-bottom: 0.5rem;
62 }
63 .protocol {
64 background: #f8f9fa;
65 padding: 1rem;
66 border-radius: 8px;
67 margin-bottom: 1rem;
68 border-left: 4px solid #667eea;
69 }
70 .protocol h3 {
71 color: #333;
72 margin-bottom: 0.5rem;
73 font-size: 1.2rem;
74 }
75 .protocol code {
76 background: #e9ecef;
77 padding: 0.2rem 0.5rem;
78 border-radius: 4px;
79 font-family: "SF Mono", Monaco, "Cascadia Code", "Roboto Mono", Consolas, monospace;
80 font-size: 0.9rem;
81 color: #764ba2;
82 }
83 .protocol p {
84 margin: 0.5rem 0;
85 color: #666;
86 }
87 .nips {
88 display: flex;
89 flex-wrap: wrap;
90 gap: 0.5rem;
91 }
92 .nip {
93 background: #667eea;
94 color: white;
95 padding: 0.4rem 0.8rem;
96 border-radius: 20px;
97 font-size: 0.9rem;
98 font-weight: 500;
99 }
100 .footer {
101 background: #f8f9fa;
102 padding: 1.5rem;
103 text-align: center;
104 color: #666;
105 font-size: 0.9rem;
106 }
107 .footer a {
108 color: #667eea;
109 text-decoration: none;
110 }
111 .footer a:hover {
112 text-decoration: underline;
113 }
114 @media (max-width: 600px) {
115 body {
116 padding: 1rem;
117 }
118 .header h1 {
119 font-size: 2rem;
120 }
121 .content {
122 padding: 1.5rem;
123 }
124 }
125 </style>
126</head>
127<body>
128 <div class="container">
129 <div class="header">
130 <h1>⚡ Nostr gRPC Relay</h1>
131 <p>High-performance relay with multi-protocol support</p>
132 </div>
133
134 <div class="content">
135 <div class="section">
136 <h2>Protocols</h2>
137
138 <div class="protocol">
139 <h3>🔌 gRPC (Native Binary)</h3>
140 <p><code>{{.GrpcAddr}}</code></p>
141 <p>High-performance binary protocol for applications</p>
142 </div>
143
144 <div class="protocol">
145 <h3>🌐 Connect (HTTP/JSON)</h3>
146 <p><code>{{.HttpAddr}}/nostr.v1.NostrRelay/*</code></p>
147 <p>Browser-compatible gRPC over HTTP with JSON</p>
148 </div>
149
150 <div class="protocol">
151 <h3>🔗 WebSocket (Nostr Protocol)</h3>
152 <p><code>ws://{{.WsAddr}}/</code></p>
153 <p>Standard Nostr protocol (NIP-01) for all clients</p>
154 </div>
155 </div>
156
157 <div class="section">
158 <h2>Supported NIPs</h2>
159 <div class="nips">
160 <span class="nip">NIP-01</span>
161 <span class="nip">NIP-09</span>
162 <span class="nip">NIP-11</span>
163 </div>
164 </div>
165
166 <div class="section">
167 <h2>Features</h2>
168 <ul style="list-style-position: inside; color: #666;">
169 <li>Binary-first storage (Protocol Buffers)</li>
170 <li>SQLite with WAL mode</li>
171 <li>Event validation (ID & signature)</li>
172 <li>Real-time subscriptions</li>
173 <li>Event deletion (NIP-09)</li>
174 </ul>
175 </div>
176 </div>
177
178 <div class="footer">
179 <p>Built with <a href="https://github.com/anthropics/claude-code" target="_blank">Claude Code</a></p>
180 </div>
181 </div>
182</body>
183</html>`))
184
185type IndexData struct {
186 GrpcAddr string
187 HttpAddr string
188 WsAddr string
189}
190
191func (h *Handler) ServeIndex(w http.ResponseWriter, r *http.Request, data IndexData) {
192 w.Header().Set("Content-Type", "text/html; charset=utf-8")
193 indexTemplate.Execute(w, data)
194}