summaryrefslogtreecommitdiffstats
path: root/internal/handler
diff options
context:
space:
mode:
Diffstat (limited to 'internal/handler')
-rw-r--r--internal/handler/websocket/index.go468
1 files changed, 367 insertions, 101 deletions
diff --git a/internal/handler/websocket/index.go b/internal/handler/websocket/index.go
index 96bcfd4..861fbb1 100644
--- a/internal/handler/websocket/index.go
+++ b/internal/handler/websocket/index.go
@@ -10,173 +10,439 @@ var indexTemplate = template.Must(template.New("index").Parse(`<!DOCTYPE html>
10<head> 10<head>
11 <meta charset="UTF-8"> 11 <meta charset="UTF-8">
12 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 12 <meta name="viewport" content="width=device-width, initial-scale=1.0">
13 <title>Nostr gRPC Relay</title> 13 <title>NOSTR-GRPC-RELAY/v1.0.0</title>
14 <link rel="preconnect" href="https://fonts.googleapis.com">
15 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
16 <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;700&display=swap" rel="stylesheet">
14 <style> 17 <style>
18 :root {
19 --bg-primary: #0a0e14;
20 --bg-secondary: #151a23;
21 --bg-tertiary: #1f2531;
22 --cyan: #00ff9f;
23 --green: #39ff14;
24 --white: #e6e6e6;
25 --gray: #6b7280;
26 --red: #ff3366;
27 --border: #2a3142;
28 }
29
15 * { 30 * {
16 margin: 0; 31 margin: 0;
17 padding: 0; 32 padding: 0;
18 box-sizing: border-box; 33 box-sizing: border-box;
19 } 34 }
35
20 body { 36 body {
21 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; 37 font-family: 'JetBrains Mono', monospace;
22 line-height: 1.6; 38 background: var(--bg-primary);
23 color: #333; 39 color: var(--white);
24 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
25 min-height: 100vh; 40 min-height: 100vh;
26 padding: 2rem; 41 overflow-x: hidden;
42 position: relative;
27 } 43 }
44
45 /* Animated grid background */
46 body::before {
47 content: '';
48 position: fixed;
49 top: 0;
50 left: 0;
51 width: 100%;
52 height: 100%;
53 background-image:
54 repeating-linear-gradient(0deg, transparent, transparent 2px, var(--border) 2px, var(--border) 3px),
55 repeating-linear-gradient(90deg, transparent, transparent 2px, var(--border) 2px, var(--border) 3px);
56 background-size: 50px 50px;
57 opacity: 0.15;
58 pointer-events: none;
59 animation: gridScroll 20s linear infinite;
60 z-index: 0;
61 }
62
63 @keyframes gridScroll {
64 0% { transform: translate(0, 0); }
65 100% { transform: translate(50px, 50px); }
66 }
67
28 .container { 68 .container {
29 max-width: 800px; 69 position: relative;
70 z-index: 1;
71 max-width: 1200px;
30 margin: 0 auto; 72 margin: 0 auto;
31 background: white; 73 padding: 2rem;
32 border-radius: 12px; 74 }
33 box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); 75
34 overflow: hidden; 76 /* Status bar */
77 .status-bar {
78 display: flex;
79 justify-content: space-between;
80 align-items: center;
81 padding: 1rem;
82 background: var(--bg-secondary);
83 border: 1px solid var(--border);
84 margin-bottom: 2rem;
85 font-size: 0.75rem;
86 font-weight: 300;
87 animation: fadeIn 0.5s ease-out;
88 }
89
90 .status-item {
91 display: flex;
92 align-items: center;
93 gap: 0.5rem;
35 } 94 }
95
96 .status-indicator {
97 width: 8px;
98 height: 8px;
99 background: var(--green);
100 border-radius: 50%;
101 animation: pulse 2s ease-in-out infinite;
102 }
103
104 @keyframes pulse {
105 0%, 100% { opacity: 1; }
106 50% { opacity: 0.4; }
107 }
108
109 /* Header with ASCII art */
36 .header { 110 .header {
37 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 111 margin-bottom: 3rem;
38 color: white; 112 animation: fadeIn 0.8s ease-out 0.2s both;
39 padding: 2rem; 113 }
40 text-align: center; 114
115 .ascii-logo {
116 font-size: 0.5rem;
117 line-height: 0.5rem;
118 color: var(--cyan);
119 white-space: pre;
120 margin-bottom: 1.5rem;
121 filter: drop-shadow(0 0 10px rgba(0, 255, 159, 0.3));
122 animation: glitchLogo 3s ease-in-out infinite;
41 } 123 }
124
125 @keyframes glitchLogo {
126 0%, 90%, 100% { transform: translate(0); }
127 92% { transform: translate(-2px, 1px); }
128 94% { transform: translate(2px, -1px); }
129 96% { transform: translate(-1px, 2px); }
130 }
131
42 .header h1 { 132 .header h1 {
43 font-size: 2.5rem; 133 font-size: 2rem;
134 font-weight: 700;
135 color: var(--cyan);
44 margin-bottom: 0.5rem; 136 margin-bottom: 0.5rem;
137 letter-spacing: 0.1em;
45 } 138 }
46 .header p { 139
47 font-size: 1.1rem; 140 .header .subtitle {
48 opacity: 0.9; 141 color: var(--gray);
142 font-size: 0.875rem;
143 font-weight: 300;
49 } 144 }
50 .content { 145
51 padding: 2rem; 146 .header .subtitle::before {
147 content: '> ';
148 color: var(--green);
52 } 149 }
53 .section { 150
54 margin-bottom: 2rem; 151 /* Protocol blocks */
152 .protocols {
153 display: grid;
154 grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
155 gap: 1.5rem;
156 margin-bottom: 3rem;
55 } 157 }
56 .section h2 { 158
57 color: #667eea; 159 .protocol-card {
58 margin-bottom: 1rem; 160 background: var(--bg-secondary);
59 font-size: 1.5rem; 161 border: 1px solid var(--border);
60 border-bottom: 2px solid #f0f0f0; 162 padding: 1.5rem;
61 padding-bottom: 0.5rem; 163 position: relative;
164 overflow: hidden;
165 animation: fadeInUp 0.8s ease-out backwards;
62 } 166 }
63 .protocol { 167
64 background: #f8f9fa; 168 .protocol-card:nth-child(1) { animation-delay: 0.3s; }
65 padding: 1rem; 169 .protocol-card:nth-child(2) { animation-delay: 0.4s; }
66 border-radius: 8px; 170 .protocol-card:nth-child(3) { animation-delay: 0.5s; }
171
172 .protocol-card::before {
173 content: '';
174 position: absolute;
175 top: 0;
176 left: 0;
177 width: 100%;
178 height: 2px;
179 background: linear-gradient(90deg, var(--cyan), transparent);
180 animation: scanLine 2s ease-in-out infinite;
181 }
182
183 @keyframes scanLine {
184 0%, 100% { transform: translateX(-100%); }
185 50% { transform: translateX(100%); }
186 }
187
188 .protocol-card:hover {
189 border-color: var(--cyan);
190 box-shadow: 0 0 20px rgba(0, 255, 159, 0.1);
191 transition: all 0.3s ease;
192 }
193
194 .protocol-header {
195 display: flex;
196 justify-content: space-between;
197 align-items: center;
67 margin-bottom: 1rem; 198 margin-bottom: 1rem;
68 border-left: 4px solid #667eea;
69 } 199 }
70 .protocol h3 { 200
71 color: #333; 201 .protocol-name {
72 margin-bottom: 0.5rem; 202 font-size: 0.875rem;
73 font-size: 1.2rem; 203 font-weight: 700;
204 color: var(--green);
205 letter-spacing: 0.05em;
74 } 206 }
75 .protocol code { 207
76 background: #e9ecef; 208 .protocol-status {
77 padding: 0.2rem 0.5rem; 209 font-size: 0.75rem;
78 border-radius: 4px; 210 color: var(--gray);
79 font-family: "SF Mono", Monaco, "Cascadia Code", "Roboto Mono", Consolas, monospace; 211 display: flex;
80 font-size: 0.9rem; 212 align-items: center;
81 color: #764ba2; 213 gap: 0.5rem;
82 } 214 }
83 .protocol p { 215
84 margin: 0.5rem 0; 216 .protocol-status::before {
85 color: #666; 217 content: '●';
218 color: var(--green);
86 } 219 }
87 .nips { 220
221 .protocol-endpoint {
222 background: var(--bg-tertiary);
223 padding: 0.75rem;
224 margin: 1rem 0;
225 border-left: 2px solid var(--cyan);
226 font-size: 0.75rem;
227 color: var(--cyan);
228 word-break: break-all;
229 font-weight: 400;
230 }
231
232 .protocol-desc {
233 font-size: 0.75rem;
234 color: var(--gray);
235 font-weight: 300;
236 line-height: 1.5;
237 }
238
239 /* Info section */
240 .info-grid {
241 display: grid;
242 grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
243 gap: 1.5rem;
244 margin-bottom: 3rem;
245 }
246
247 .info-card {
248 background: var(--bg-secondary);
249 border: 1px solid var(--border);
250 padding: 1.5rem;
251 animation: fadeInUp 0.8s ease-out 0.6s backwards;
252 }
253
254 .info-card h3 {
255 font-size: 0.75rem;
256 color: var(--cyan);
257 margin-bottom: 1rem;
258 letter-spacing: 0.1em;
259 font-weight: 700;
260 }
261
262 .nip-list {
88 display: flex; 263 display: flex;
89 flex-wrap: wrap; 264 flex-wrap: wrap;
90 gap: 0.5rem; 265 gap: 0.5rem;
91 } 266 }
92 .nip { 267
93 background: #667eea; 268 .nip-tag {
94 color: white; 269 background: var(--bg-tertiary);
95 padding: 0.4rem 0.8rem; 270 border: 1px solid var(--border);
96 border-radius: 20px; 271 padding: 0.25rem 0.75rem;
97 font-size: 0.9rem; 272 font-size: 0.75rem;
273 color: var(--green);
98 font-weight: 500; 274 font-weight: 500;
275 transition: all 0.2s ease;
276 }
277
278 .nip-tag:hover {
279 border-color: var(--green);
280 box-shadow: 0 0 10px rgba(57, 255, 20, 0.2);
281 }
282
283 .feature-list {
284 list-style: none;
285 font-size: 0.75rem;
286 color: var(--gray);
287 font-weight: 300;
288 }
289
290 .feature-list li {
291 padding: 0.5rem 0;
292 border-bottom: 1px solid var(--border);
293 }
294
295 .feature-list li:last-child {
296 border-bottom: none;
99 } 297 }
298
299 .feature-list li::before {
300 content: '[✓] ';
301 color: var(--green);
302 margin-right: 0.5rem;
303 }
304
305 /* Footer */
100 .footer { 306 .footer {
101 background: #f8f9fa;
102 padding: 1.5rem;
103 text-align: center; 307 text-align: center;
104 color: #666; 308 padding: 2rem;
105 font-size: 0.9rem; 309 border-top: 1px solid var(--border);
310 margin-top: 3rem;
311 font-size: 0.75rem;
312 color: var(--gray);
313 animation: fadeIn 0.8s ease-out 0.8s both;
106 } 314 }
107 .footer a { 315
108 color: #667eea; 316 .footer pre {
109 text-decoration: none; 317 margin: 1rem 0;
318 color: var(--cyan);
319 opacity: 0.5;
320 font-size: 0.5rem;
321 line-height: 0.6rem;
110 } 322 }
111 .footer a:hover { 323
112 text-decoration: underline; 324 @keyframes fadeIn {
325 from { opacity: 0; }
326 to { opacity: 1; }
113 } 327 }
114 @media (max-width: 600px) { 328
115 body { 329 @keyframes fadeInUp {
330 from {
331 opacity: 0;
332 transform: translateY(20px);
333 }
334 to {
335 opacity: 1;
336 transform: translateY(0);
337 }
338 }
339
340 @media (max-width: 768px) {
341 .container {
116 padding: 1rem; 342 padding: 1rem;
117 } 343 }
344 .ascii-logo {
345 font-size: 0.35rem;
346 line-height: 0.4rem;
347 }
118 .header h1 { 348 .header h1 {
119 font-size: 2rem; 349 font-size: 1.5rem;
120 } 350 }
121 .content { 351 .status-bar {
122 padding: 1.5rem; 352 flex-direction: column;
353 gap: 0.5rem;
354 align-items: flex-start;
123 } 355 }
124 } 356 }
125 </style> 357 </style>
126</head> 358</head>
127<body> 359<body>
128 <div class="container"> 360 <div class="container">
129 <div class="header"> 361 <div class="status-bar">
130 <h1>⚡ Nostr gRPC Relay</h1> 362 <div class="status-item">
131 <p>High-performance relay with multi-protocol support</p> 363 <span class="status-indicator"></span>
364 <span>RELAY.ONLINE</span>
365 </div>
366 <div class="status-item">
367 <span>PROTOCOLS: 3</span>
368 </div>
369 <div class="status-item">
370 <span>NIPs: 01/09/11</span>
371 </div>
132 </div> 372 </div>
133 373
134 <div class="content"> 374 <div class="header">
135 <div class="section"> 375 <pre class="ascii-logo">
136 <h2>Protocols</h2> 376 ███▄ █ ▒█████ ██████ ▄▄▄█████▓ ██▀███
377 ██ ▀█ █ ▒██▒ ██▒▒██ ▒ ▓ ██▒ ▓▒▓██ ▒ ██▒
378▓██ ▀█ ██▒▒██░ ██▒░ ▓██▄ ▒ ▓██░ ▒░▓██ ░▄█ ▒
379▓██▒ ▐▌██▒▒██ ██░ ▒ ██▒░ ▓██▓ ░ ▒██▀▀█▄
380▒██░ ▓██░░ ████▓▒░▒██████▒▒ ▒██▒ ░ ░██▓ ▒██▒
381░ ▒░ ▒ ▒ ░ ▒░▒░▒░ ▒ ▒▓▒ ▒ ░ ▒ ░░ ░ ▒▓ ░▒▓░
382░ ░░ ░ ▒░ ░ ▒ ▒░ ░ ░▒ ░ ░ ░ ░▒ ░ ▒░
383 ░ ░ ░ ░ ░ ░ ▒ ░ ░ ░ ░ ░░ ░
384 ░ ░ ░ ░ ░ </pre>
385 <h1>NOSTR-GRPC-RELAY</h1>
386 <p class="subtitle">decentralized protocol node // multi-transport relay</p>
387 </div>
137 388
138 <div class="protocol"> 389 <div class="protocols">
139 <h3>🔌 gRPC (Native Binary)</h3> 390 <div class="protocol-card">
140 <p><code>{{.GrpcAddr}}</code></p> 391 <div class="protocol-header">
141 <p>High-performance binary protocol for applications</p> 392 <span class="protocol-name">gRPC</span>
393 <span class="protocol-status">ACTIVE</span>
142 </div> 394 </div>
395 <div class="protocol-endpoint">{{.GrpcAddr}}</div>
396 <p class="protocol-desc">Native binary protocol. High-throughput RPC interface for application-layer integration. Protocol Buffers over HTTP/2.</p>
397 </div>
143 398
144 <div class="protocol"> 399 <div class="protocol-card">
145 <h3>🌐 Connect (HTTP/JSON)</h3> 400 <div class="protocol-header">
146 <p><code>{{.HttpAddr}}/nostr.v1.NostrRelay/*</code></p> 401 <span class="protocol-name">CONNECT</span>
147 <p>Browser-compatible gRPC over HTTP with JSON</p> 402 <span class="protocol-status">ACTIVE</span>
148 </div> 403 </div>
404 <div class="protocol-endpoint">{{.HttpAddr}}/nostr.v1.NostrRelay/*</div>
405 <p class="protocol-desc">Browser-compatible HTTP/JSON interface. gRPC-Web with Connect protocol. CORS-enabled for web clients.</p>
406 </div>
149 407
150 <div class="protocol"> 408 <div class="protocol-card">
151 <h3>🔗 WebSocket (Nostr Protocol)</h3> 409 <div class="protocol-header">
152 <p><code>ws://{{.WsAddr}}/</code></p> 410 <span class="protocol-name">WEBSOCKET</span>
153 <p>Standard Nostr protocol (NIP-01) for all clients</p> 411 <span class="protocol-status">ACTIVE</span>
154 </div> 412 </div>
413 <div class="protocol-endpoint">ws://{{.WsAddr}}/</div>
414 <p class="protocol-desc">Standard Nostr protocol (NIP-01). Compatible with all Nostr clients. Real-time event streaming.</p>
155 </div> 415 </div>
416 </div>
156 417
157 <div class="section"> 418 <div class="info-grid">
158 <h2>Supported NIPs</h2> 419 <div class="info-card">
159 <div class="nips"> 420 <h3>SUPPORTED_NIPS</h3>
160 <span class="nip">NIP-01</span> 421 <div class="nip-list">
161 <span class="nip">NIP-09</span> 422 <span class="nip-tag">NIP-01</span>
162 <span class="nip">NIP-11</span> 423 <span class="nip-tag">NIP-09</span>
424 <span class="nip-tag">NIP-11</span>
163 </div> 425 </div>
164 </div> 426 </div>
165 427
166 <div class="section"> 428 <div class="info-card">
167 <h2>Features</h2> 429 <h3>RELAY_FEATURES</h3>
168 <ul style="list-style-position: inside; color: #666;"> 430 <ul class="feature-list">
169 <li>Binary-first storage (Protocol Buffers)</li> 431 <li>Binary-first storage</li>
170 <li>SQLite with WAL mode</li> 432 <li>SQLite WAL mode</li>
171 <li>Event validation (ID & signature)</li> 433 <li>Event validation</li>
172 <li>Real-time subscriptions</li> 434 <li>Real-time subscriptions</li>
173 <li>Event deletion (NIP-09)</li> 435 <li>Hard deletion (NIP-09)</li>
174 </ul> 436 </ul>
175 </div> 437 </div>
176 </div> 438 </div>
177 439
178 <div class="footer"> 440 <div class="footer">
179 <p>Built with <a href="https://github.com/anthropics/claude-code" target="_blank">Claude Code</a></p> 441 <pre>
442┌─────────────────────────────────────┐
443│ DECENTRALIZED // CENSORSHIP-RESISTANT │
444└─────────────────────────────────────┘</pre>
445 <p>relay.status = online // uptime.monitor = true</p>
180 </div> 446 </div>
181 </div> 447 </div>
182</body> 448</body>