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