handle-bunker.go raw
1 package app
2
3 import (
4 "encoding/json"
5 "net/http"
6 "strings"
7
8 "next.orly.dev/pkg/nostr/encoders/bech32encoding"
9 "next.orly.dev/pkg/nostr/encoders/hex"
10 "next.orly.dev/pkg/nostr/interfaces/signer/p8k"
11 "next.orly.dev/pkg/lol/chk"
12 "next.orly.dev/pkg/lol/log"
13 )
14
15 // BunkerInfoResponse is returned by the /api/bunker/info endpoint.
16 type BunkerInfoResponse struct {
17 RelayURL string `json:"relay_url"` // WebSocket URL for NIP-46 connections
18 RelayNpub string `json:"relay_npub"` // Relay's npub
19 RelayPubkey string `json:"relay_pubkey"` // Relay's hex pubkey
20 ACLMode string `json:"acl_mode"` // Current ACL mode
21 Available bool `json:"available"` // Whether bunker is available
22 }
23
24 // handleBunkerInfo returns bunker connection information.
25 // This is a public endpoint that doesn't require authentication.
26 func (s *Server) handleBunkerInfo(w http.ResponseWriter, r *http.Request) {
27 if r.Method != http.MethodGet {
28 http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
29 return
30 }
31
32 // Get relay identity
33 relaySecret, err := s.DB.GetOrCreateRelayIdentitySecret()
34 if chk.E(err) {
35 log.E.F("failed to get relay identity: %v", err)
36 http.Error(w, "Failed to get relay identity", http.StatusInternalServerError)
37 return
38 }
39
40 // Derive public key
41 sign, err := p8k.New()
42 if chk.E(err) {
43 http.Error(w, "Failed to create signer", http.StatusInternalServerError)
44 return
45 }
46 if err := sign.InitSec(relaySecret); chk.E(err) {
47 http.Error(w, "Failed to initialize signer", http.StatusInternalServerError)
48 return
49 }
50 relayPubkey := sign.Pub()
51 relayPubkeyHex := hex.Enc(relayPubkey)
52
53 // Encode as npub
54 relayNpubBytes, err := bech32encoding.BinToNpub(relayPubkey)
55 relayNpub := string(relayNpubBytes)
56 if chk.E(err) {
57 relayNpub = relayPubkeyHex // Fallback to hex
58 }
59
60 // Build WebSocket URL from service URL
61 serviceURL := s.ServiceURL(r)
62 wsURL := strings.Replace(serviceURL, "https://", "wss://", 1)
63 wsURL = strings.Replace(wsURL, "http://", "ws://", 1)
64
65 // Bunker is available when ACL mode is not "none"
66 available := s.Config.ACLMode != "none"
67
68 resp := BunkerInfoResponse{
69 RelayURL: wsURL,
70 RelayNpub: relayNpub,
71 RelayPubkey: relayPubkeyHex,
72 ACLMode: s.Config.ACLMode,
73 Available: available,
74 }
75
76 w.Header().Set("Content-Type", "application/json")
77 json.NewEncoder(w).Encode(resp)
78 }
79