Files
nostrlib/khatru/relay.go

163 lines
5.1 KiB
Go

package khatru
import (
"context"
"iter"
"log"
"net/http"
"os"
"strconv"
"strings"
"sync"
"time"
"fiatjaf.com/nostr"
"fiatjaf.com/nostr/eventstore"
"fiatjaf.com/nostr/nip11"
"fiatjaf.com/nostr/nip45/hyperloglog"
"github.com/fasthttp/websocket"
)
func NewRelay() *Relay {
ctx := context.Background()
rl := &Relay{
Log: log.New(os.Stderr, "[khatru-relay] ", log.LstdFlags),
Info: &nip11.RelayInformationDocument{
Software: "https://fiatjaf.com/nostr/khatru",
Version: "n/a",
SupportedNIPs: []any{1, 11, 40, 42, 70, 86},
},
upgrader: websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool { return true },
},
clients: make(map[*WebSocket][]listenerSpec, 100),
listeners: make([]listener, 0, 100),
serveMux: &http.ServeMux{},
WriteWait: 10 * time.Second,
PongWait: 60 * time.Second,
PingPeriod: 30 * time.Second,
MaxMessageSize: 512000,
}
rl.expirationManager = newExpirationManager(rl)
go rl.expirationManager.start(ctx)
return rl
}
type Relay struct {
// setting this variable overwrites the hackish workaround we do to try to figure out our own base URL
ServiceURL string
// hooks that will be called at various times
OnEvent func(ctx context.Context, event nostr.Event) (reject bool, msg string)
StoreEvent func(ctx context.Context, event nostr.Event) error
ReplaceEvent func(ctx context.Context, event nostr.Event) error
DeleteEvent func(ctx context.Context, id nostr.ID) error
OnEventSaved func(ctx context.Context, event nostr.Event)
OnEphemeralEvent func(ctx context.Context, event nostr.Event)
OnRequest func(ctx context.Context, filter nostr.Filter) (reject bool, msg string)
OnCountFilter func(ctx context.Context, filter nostr.Filter) (reject bool, msg string)
QueryStored func(ctx context.Context, filter nostr.Filter) iter.Seq[nostr.Event]
Count func(ctx context.Context, filter nostr.Filter) (uint32, error)
CountHLL func(ctx context.Context, filter nostr.Filter, offset int) (uint32, *hyperloglog.HyperLogLog, error)
RejectConnection func(r *http.Request) bool
OnConnect func(ctx context.Context)
OnDisconnect func(ctx context.Context)
OverwriteRelayInformation func(ctx context.Context, r *http.Request, info nip11.RelayInformationDocument) nip11.RelayInformationDocument
PreventBroadcast func(ws *WebSocket, event nostr.Event) bool
// these are used when this relays acts as a router
routes []Route
getSubRelayFromEvent func(*nostr.Event) *Relay // used for handling EVENTs
getSubRelayFromFilter func(nostr.Filter) *Relay // used for handling REQs
// setting up handlers here will enable these methods
ManagementAPI RelayManagementAPI
// editing info will affect the NIP-11 responses
Info *nip11.RelayInformationDocument
// Default logger, as set by NewServer, is a stdlib logger prefixed with "[khatru-relay] ",
// outputting to stderr.
Log *log.Logger
// for establishing websockets
upgrader websocket.Upgrader
// keep a connection reference to all connected clients for Server.Shutdown
// also used for keeping track of who is listening to what
clients map[*WebSocket][]listenerSpec
listeners []listener
clientsMutex sync.Mutex
// set this to true to support negentropy
Negentropy bool
// in case you call Server.Start
Addr string
serveMux *http.ServeMux
httpServer *http.Server
// websocket options
WriteWait time.Duration // Time allowed to write a message to the peer.
PongWait time.Duration // Time allowed to read the next pong message from the peer.
PingPeriod time.Duration // Send pings to peer with this period. Must be less than pongWait.
MaxMessageSize int64 // Maximum message size allowed from peer.
// NIP-40 expiration manager
expirationManager *expirationManager
}
func (rl *Relay) UseEventstore(store eventstore.Store) {
rl.QueryStored = func(ctx context.Context, filter nostr.Filter) iter.Seq[nostr.Event] {
return store.QueryEvents(filter)
}
rl.Count = func(ctx context.Context, filter nostr.Filter) (uint32, error) {
return store.CountEvents(filter)
}
rl.StoreEvent = func(ctx context.Context, event nostr.Event) error {
return store.SaveEvent(event)
}
rl.ReplaceEvent = func(ctx context.Context, event nostr.Event) error {
return store.ReplaceEvent(event)
}
rl.DeleteEvent = func(ctx context.Context, id nostr.ID) error {
return store.DeleteEvent(id)
}
}
func (rl *Relay) getBaseURL(r *http.Request) string {
if rl.ServiceURL != "" {
return rl.ServiceURL
}
host := r.Header.Get("X-Forwarded-Host")
if host == "" {
host = r.Host
}
proto := r.Header.Get("X-Forwarded-Proto")
if proto == "" {
if host == "localhost" {
proto = "http"
} else if strings.Contains(host, ":") {
// has a port number
proto = "http"
} else if _, err := strconv.Atoi(strings.ReplaceAll(host, ".", "")); err == nil {
// it's a naked IP
proto = "http"
} else {
proto = "https"
}
}
return proto + "://" + host
}