client.go raw
1 package main
2
3 import (
4 "common/jsbridge/sw"
5 "common/nostr"
6 "common/relay"
7 )
8
9 // Relay Proxy domain — WebSocket connection management.
10 // Receives events from relays, routes them to the Subscription Router.
11 // Never touches cache or DM crypto directly.
12
13 var (
14 rpool *relay.Pool
15 writeRelays []string
16 )
17
18 func initRelayProxy() {
19 rpool = relay.NewPool()
20 }
21
22 // getConn gets or creates a relay connection with event routing.
23 func getConn(url string) *relay.Conn {
24 c := rpool.Connect(url)
25 wireConn(c, url)
26 return c
27 }
28
29 func wireConn(c *relay.Conn, url string) {
30 c.SetOnEvent(func(_ string, ev *nostr.Event) {
31 routerOnRelayEvent(url, ev)
32 })
33 c.SetOnEOSE(func(subID string) {
34 routerOnRelayEOSE(subID)
35 })
36 c.SetOnOK(func(eventID string, ok bool, msg string) {
37 okStr := "true"
38 if !ok {
39 okStr = "false"
40 }
41 broadcastToClients("[\"OK\"," + jstr(eventID) + "," + okStr + "," + jstr(msg) + "]")
42 })
43 c.SetOnAuth(func(challenge string) {
44 onRelayAuth(url, challenge)
45 })
46 }
47
48 func onRelayAuth(relayURL, challenge string) {
49 if !hasKey || myPubkey == "" {
50 return
51 }
52 authEv := &nostr.Event{
53 Kind: 22242,
54 Content: "",
55 Tags: nostr.Tags{{"relay", relayURL}, {"challenge", challenge}},
56 CreatedAt: sw.NowSeconds(),
57 }
58 if !identitySignEvent(authEv) {
59 return
60 }
61 c := rpool.Get(relayURL)
62 if c != nil && c.IsOpen() {
63 c.Send("[\"AUTH\"," + authEv.ToJSON() + "]")
64 }
65 }
66
67 // relayPublish sends an event to all write relays.
68 func relayPublish(ev *nostr.Event) {
69 for _, url := range writeRelays {
70 getConn(url).Publish(ev)
71 }
72 }
73
74 // relayPublishExcept sends an event to all write relays except the specified one.
75 func relayPublishExcept(ev *nostr.Event, exceptURL string) {
76 for _, wr := range writeRelays {
77 if wr != exceptURL {
78 getConn(wr).Publish(ev)
79 }
80 }
81 }
82
83 func urlSuffix(url string) string {
84 n := min(len(url), 8)
85 out := make([]byte, 0, n)
86 for i := len(url) - n; i < len(url); i++ {
87 c := url[i]
88 if (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') {
89 out = append(out, c)
90 }
91 }
92 return string(out)
93 }
94
95 func handleRelayInfo(clientID, relayURL string) {
96 httpURL := relayURL
97 if len(httpURL) > 6 && httpURL[:6] == "wss://" {
98 httpURL = "https://" + httpURL[6:]
99 } else if len(httpURL) > 5 && httpURL[:5] == "ws://" {
100 httpURL = "http://" + httpURL[5:]
101 }
102 // NIP-11 relay info is fetched app-side via dom.FetchRelayInfo.
103 // SW stub sends null to maintain protocol compatibility.
104 sw.Fetch(httpURL, func(resp sw.Response, ok bool) {
105 sendToClient(clientID, "[\"RELAY_INFO\","+jstr(relayURL)+",null]")
106 })
107 }
108