khatru: get rid of subrelays + segregated indexed listeners.
This commit is contained in:
@@ -44,6 +44,7 @@ func (ef Filter) Matches(event Event) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//go:inline
|
||||||
func (ef Filter) MatchesIgnoringTimestampConstraints(event Event) bool {
|
func (ef Filter) MatchesIgnoringTimestampConstraints(event Event) bool {
|
||||||
if ef.IDs != nil && !slices.Contains(ef.IDs, event.ID) {
|
if ef.IDs != nil && !slices.Contains(ef.IDs, event.ID) {
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -1,58 +0,0 @@
|
|||||||
---
|
|
||||||
outline: deep
|
|
||||||
---
|
|
||||||
|
|
||||||
# Request Routing
|
|
||||||
|
|
||||||
If you have one (or more) set of policies that have to be executed in sequence (for example, first you check for the presence of a tag, then later in the next policies you use that tag without checking) and they only apply to some class of events, but you still want your relay to deal with other classes of events that can lead to cumbersome sets of rules, always having to check if an event meets the requirements and so on. There is where routing can help you.
|
|
||||||
|
|
||||||
```go
|
|
||||||
sk := os.Getenv("RELAY_SECRET_KEY")
|
|
||||||
|
|
||||||
// a relay for NIP-29 groups
|
|
||||||
groupsStore := boltdb.BoltBackend{}
|
|
||||||
groupsStore.Init()
|
|
||||||
groupsRelay, _ := khatru29.Init(relay29.Options{Domain: "example.com", DB: groupsStore, SecretKey: sk})
|
|
||||||
// ...
|
|
||||||
|
|
||||||
// a relay for everything else
|
|
||||||
publicStore := slicestore.SliceStore{}
|
|
||||||
publicStore.Init()
|
|
||||||
publicRelay := khatru.NewRelay()
|
|
||||||
publicRelay.UseEventStore(publicStore, 1000)
|
|
||||||
// ...
|
|
||||||
|
|
||||||
// a higher-level relay that just routes between the two above
|
|
||||||
router := khatru.NewRouter()
|
|
||||||
|
|
||||||
// route requests and events to the groups relay
|
|
||||||
router.Route().
|
|
||||||
Req(func (filter nostr.Filter) bool {
|
|
||||||
_, hasHTag := filter.Tags["h"]
|
|
||||||
if hasHTag {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return slices.Contains(filter.Kinds, func (k int) bool { return k == 39000 || k == 39001 || k == 39002 })
|
|
||||||
}).
|
|
||||||
Event(func (event *nostr.Event) bool {
|
|
||||||
switch {
|
|
||||||
case event.Kind <= 9021 && event.Kind >= 9000:
|
|
||||||
return true
|
|
||||||
case event.Kind <= 39010 && event.Kind >= 39000:
|
|
||||||
return true
|
|
||||||
case event.Kind <= 12 && event.Kind >= 9:
|
|
||||||
return true
|
|
||||||
case event.Tags.Find("h") != nil:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}).
|
|
||||||
Relay(groupsRelay)
|
|
||||||
|
|
||||||
// route requests and events to the other
|
|
||||||
router.Route().
|
|
||||||
Req(func (filter nostr.Filter) bool { return true }).
|
|
||||||
Event(func (event *nostr.Event) bool { return true }).
|
|
||||||
Relay(publicRelay)
|
|
||||||
```
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"slices"
|
|
||||||
|
|
||||||
"fiatjaf.com/nostr"
|
|
||||||
"fiatjaf.com/nostr/eventstore/lmdb"
|
|
||||||
"fiatjaf.com/nostr/eventstore/slicestore"
|
|
||||||
"fiatjaf.com/nostr/khatru"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
db1 := &slicestore.SliceStore{}
|
|
||||||
db1.Init()
|
|
||||||
r1 := khatru.NewRelay()
|
|
||||||
r1.UseEventstore(db1, 400)
|
|
||||||
|
|
||||||
db2 := &lmdb.LMDBBackend{Path: "/tmp/t"}
|
|
||||||
db2.Init()
|
|
||||||
r2 := khatru.NewRelay()
|
|
||||||
r2.UseEventstore(db2, 400)
|
|
||||||
|
|
||||||
db3 := &slicestore.SliceStore{}
|
|
||||||
db3.Init()
|
|
||||||
r3 := khatru.NewRelay()
|
|
||||||
r3.UseEventstore(db3, 400)
|
|
||||||
|
|
||||||
router := khatru.NewRouter()
|
|
||||||
|
|
||||||
router.Route().
|
|
||||||
Req(func(filter nostr.Filter) bool {
|
|
||||||
return slices.Contains(filter.Kinds, 30023)
|
|
||||||
}).
|
|
||||||
Event(func(event *nostr.Event) bool {
|
|
||||||
return event.Kind == 30023
|
|
||||||
}).
|
|
||||||
Relay(r1)
|
|
||||||
|
|
||||||
router.Route().
|
|
||||||
Req(func(filter nostr.Filter) bool {
|
|
||||||
return slices.Contains(filter.Kinds, 1) && slices.Contains(filter.Tags["t"], "spam")
|
|
||||||
}).
|
|
||||||
Event(func(event *nostr.Event) bool {
|
|
||||||
return event.Kind == 1 && event.Tags.FindWithValue("t", "spam") != nil
|
|
||||||
}).
|
|
||||||
Relay(r2)
|
|
||||||
|
|
||||||
router.Route().
|
|
||||||
Req(func(filter nostr.Filter) bool {
|
|
||||||
return slices.Contains(filter.Kinds, 1)
|
|
||||||
}).
|
|
||||||
Event(func(event *nostr.Event) bool {
|
|
||||||
return event.Kind == 1
|
|
||||||
}).
|
|
||||||
Relay(r3)
|
|
||||||
|
|
||||||
fmt.Println("running on :3334")
|
|
||||||
http.ListenAndServe(":3334", router)
|
|
||||||
}
|
|
||||||
@@ -61,5 +61,5 @@ func (rl *Relay) Shutdown(ctx context.Context) {
|
|||||||
ws.conn.Close()
|
ws.conn.Close()
|
||||||
}
|
}
|
||||||
clear(rl.clients)
|
clear(rl.clients)
|
||||||
rl.listeners = rl.listeners[:0]
|
rl.dispatcher = newDispatcher()
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-32
@@ -217,35 +217,30 @@ func (rl *Relay) HandleWebsocket(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
srl := rl
|
|
||||||
if rl.getSubRelayFromEvent != nil {
|
|
||||||
srl = rl.getSubRelayFromEvent(&env.Event)
|
|
||||||
}
|
|
||||||
|
|
||||||
var ok bool
|
var ok bool
|
||||||
var writeErr error
|
var writeErr error
|
||||||
var skipBroadcast bool
|
var skipBroadcast bool
|
||||||
|
|
||||||
if env.Event.Kind == nostr.KindDeletion {
|
if env.Event.Kind == nostr.KindDeletion {
|
||||||
// store the delete event first
|
// store the delete event first
|
||||||
skipBroadcast, writeErr = srl.handleNormal(ctx, env.Event)
|
skipBroadcast, writeErr = rl.handleNormal(ctx, env.Event)
|
||||||
if writeErr == nil {
|
if writeErr == nil {
|
||||||
// this always returns "blocked: " whenever it returns an error
|
// this always returns "blocked: " whenever it returns an error
|
||||||
writeErr = srl.handleDeleteRequest(ctx, env.Event)
|
writeErr = rl.handleDeleteRequest(ctx, env.Event)
|
||||||
}
|
}
|
||||||
} else if env.Event.Kind.IsEphemeral() {
|
} else if env.Event.Kind.IsEphemeral() {
|
||||||
// this will also always return a prefixed reason
|
// this will also always return a prefixed reason
|
||||||
writeErr = srl.handleEphemeral(ctx, env.Event)
|
writeErr = rl.handleEphemeral(ctx, env.Event)
|
||||||
} else {
|
} else {
|
||||||
// this will also always return a prefixed reason
|
// this will also always return a prefixed reason
|
||||||
skipBroadcast, writeErr = srl.handleNormal(ctx, env.Event)
|
skipBroadcast, writeErr = rl.handleNormal(ctx, env.Event)
|
||||||
}
|
}
|
||||||
|
|
||||||
var reason string
|
var reason string
|
||||||
if writeErr == nil {
|
if writeErr == nil {
|
||||||
ok = true
|
ok = true
|
||||||
if !skipBroadcast {
|
if !skipBroadcast {
|
||||||
n := srl.notifyListeners(env.Event, false)
|
n := rl.notifyListeners(env.Event, false)
|
||||||
|
|
||||||
// the number of notified listeners matters in ephemeral events
|
// the number of notified listeners matters in ephemeral events
|
||||||
if env.Event.Kind.IsEphemeral() {
|
if env.Event.Kind.IsEphemeral() {
|
||||||
@@ -278,15 +273,10 @@ func (rl *Relay) HandleWebsocket(w http.ResponseWriter, r *http.Request) {
|
|||||||
var total uint32
|
var total uint32
|
||||||
var hll *hyperloglog.HyperLogLog
|
var hll *hyperloglog.HyperLogLog
|
||||||
|
|
||||||
srl := rl
|
|
||||||
if rl.getSubRelayFromFilter != nil {
|
|
||||||
srl = rl.getSubRelayFromFilter(env.Filter)
|
|
||||||
}
|
|
||||||
|
|
||||||
if offset := nip45.HyperLogLogEventPubkeyOffsetForFilter(env.Filter); offset != -1 {
|
if offset := nip45.HyperLogLogEventPubkeyOffsetForFilter(env.Filter); offset != -1 {
|
||||||
total, hll = srl.handleCountRequestWithHLL(ctx, ws, env.Filter, offset)
|
total, hll = rl.handleCountRequestWithHLL(ctx, ws, env.Filter, offset)
|
||||||
} else {
|
} else {
|
||||||
total = srl.handleCountRequest(ctx, ws, env.Filter)
|
total = rl.handleCountRequest(ctx, ws, env.Filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
resp := nostr.CountEnvelope{
|
resp := nostr.CountEnvelope{
|
||||||
@@ -311,11 +301,7 @@ func (rl *Relay) HandleWebsocket(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// handle each filter separately -- dispatching events as they're loaded from databases
|
// handle each filter separately -- dispatching events as they're loaded from databases
|
||||||
for _, filter := range env.Filters {
|
for _, filter := range env.Filters {
|
||||||
srl := rl
|
err := rl.handleRequest(reqCtx, env.SubscriptionID, &eose, ws, filter)
|
||||||
if rl.getSubRelayFromFilter != nil {
|
|
||||||
srl = rl.getSubRelayFromFilter(filter)
|
|
||||||
}
|
|
||||||
err := srl.handleRequest(reqCtx, env.SubscriptionID, &eose, ws, filter)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// fail everything if any filter is rejected
|
// fail everything if any filter is rejected
|
||||||
reason := err.Error()
|
reason := err.Error()
|
||||||
@@ -325,8 +311,11 @@ func (rl *Relay) HandleWebsocket(w http.ResponseWriter, r *http.Request) {
|
|||||||
ws.WriteJSON(nostr.ClosedEnvelope{SubscriptionID: env.SubscriptionID, Reason: reason})
|
ws.WriteJSON(nostr.ClosedEnvelope{SubscriptionID: env.SubscriptionID, Reason: reason})
|
||||||
cancelReqCtx(errors.New("filter rejected"))
|
cancelReqCtx(errors.New("filter rejected"))
|
||||||
return
|
return
|
||||||
} else {
|
} else if filter.IDs == nil {
|
||||||
rl.addListener(ws, env.SubscriptionID, srl, filter, cancelReqCtx)
|
// a query that is just a bunch of "ids": [...] will not add listeners.
|
||||||
|
// is this a bug? maybe, but I don't think anyone is listening for an ID
|
||||||
|
// that hasn't been published yet anywhere -- if yes we can change later
|
||||||
|
rl.addListener(ws, env.SubscriptionID, filter, cancelReqCtx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -363,15 +352,11 @@ func (rl *Relay) HandleWebsocket(w http.ResponseWriter, r *http.Request) {
|
|||||||
ws.WriteJSON(nostr.OKEnvelope{EventID: env.Event.ID, OK: false, Reason: "error: failed to authenticate: " + err.Error()})
|
ws.WriteJSON(nostr.OKEnvelope{EventID: env.Event.ID, OK: false, Reason: "error: failed to authenticate: " + err.Error()})
|
||||||
}
|
}
|
||||||
case *nip77.OpenEnvelope:
|
case *nip77.OpenEnvelope:
|
||||||
srl := rl
|
if !rl.Negentropy {
|
||||||
if rl.getSubRelayFromFilter != nil {
|
// ignore
|
||||||
srl = rl.getSubRelayFromFilter(env.Filter)
|
return
|
||||||
if !srl.Negentropy {
|
|
||||||
// ignore
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
vec, err := srl.startNegentropySession(ctx, env.Filter)
|
vec, err := rl.startNegentropySession(ctx, env.Filter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// fail everything if any filter is rejected
|
// fail everything if any filter is rejected
|
||||||
reason := err.Error()
|
reason := err.Error()
|
||||||
|
|||||||
+216
-75
@@ -3,18 +3,18 @@ package khatru
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"slices"
|
"iter"
|
||||||
|
|
||||||
|
"fiatjaf.com/lib/set"
|
||||||
"fiatjaf.com/nostr"
|
"fiatjaf.com/nostr"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrSubscriptionClosedByClient = errors.New("subscription closed by client")
|
var ErrSubscriptionClosedByClient = errors.New("subscription closed by client")
|
||||||
|
|
||||||
type listenerSpec struct {
|
type listenerSpec struct {
|
||||||
id string // kept here so we can easily match against it removeListenerId
|
ssid int // internal numeric id for a listener
|
||||||
cancel context.CancelCauseFunc
|
sid string // client-provided subscription id
|
||||||
index int
|
cancel context.CancelCauseFunc
|
||||||
subrelay *Relay // this is important when we're dealing with routing, otherwise it will be always the same
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type listener struct {
|
type listener struct {
|
||||||
@@ -23,10 +23,197 @@ type listener struct {
|
|||||||
ws *WebSocket
|
ws *WebSocket
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type subscription struct {
|
||||||
|
id string
|
||||||
|
filter nostr.Filter
|
||||||
|
ws *WebSocket
|
||||||
|
}
|
||||||
|
|
||||||
|
type dispatcher struct {
|
||||||
|
serial int
|
||||||
|
subscriptions map[int]subscription
|
||||||
|
byAuthor map[nostr.PubKey]set.Set[int]
|
||||||
|
byKind map[nostr.Kind]set.Set[int]
|
||||||
|
fallback set.Set[int]
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDispatcher() dispatcher {
|
||||||
|
return dispatcher{
|
||||||
|
subscriptions: make(map[int]subscription, 100),
|
||||||
|
byAuthor: make(map[nostr.PubKey]set.Set[int]),
|
||||||
|
byKind: make(map[nostr.Kind]set.Set[int]),
|
||||||
|
fallback: set.NewSliceSet[int](),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dispatcher) addSubscription(sub subscription) int {
|
||||||
|
d.serial++
|
||||||
|
ssid := d.serial
|
||||||
|
|
||||||
|
d.subscriptions[ssid] = sub
|
||||||
|
|
||||||
|
indexed := false
|
||||||
|
if sub.filter.Authors != nil {
|
||||||
|
indexed = true
|
||||||
|
for _, author := range sub.filter.Authors {
|
||||||
|
s, ok := d.byAuthor[author]
|
||||||
|
if !ok {
|
||||||
|
s = set.NewSliceSet[int]()
|
||||||
|
d.byAuthor[author] = s
|
||||||
|
}
|
||||||
|
s.Add(ssid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sub.filter.Kinds != nil {
|
||||||
|
indexed = true
|
||||||
|
for _, kind := range sub.filter.Kinds {
|
||||||
|
s, ok := d.byKind[kind]
|
||||||
|
if !ok {
|
||||||
|
s = set.NewSliceSet[int]()
|
||||||
|
d.byKind[kind] = s
|
||||||
|
}
|
||||||
|
s.Add(ssid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !indexed {
|
||||||
|
d.fallback.Add(ssid)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ssid
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dispatcher) removeSubscription(ssid int) {
|
||||||
|
sub, ok := d.subscriptions[ssid]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
delete(d.subscriptions, ssid)
|
||||||
|
|
||||||
|
indexed := false
|
||||||
|
if sub.filter.Authors != nil {
|
||||||
|
indexed = true
|
||||||
|
for _, author := range sub.filter.Authors {
|
||||||
|
s, ok := d.byAuthor[author]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.Remove(ssid)
|
||||||
|
if s.Len() == 0 {
|
||||||
|
delete(d.byAuthor, author)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sub.filter.Kinds != nil {
|
||||||
|
indexed = true
|
||||||
|
for _, kind := range sub.filter.Kinds {
|
||||||
|
s, ok := d.byKind[kind]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.Remove(ssid)
|
||||||
|
if s.Len() == 0 {
|
||||||
|
delete(d.byKind, kind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !indexed {
|
||||||
|
d.fallback.Remove(ssid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dispatcher) candidates(event nostr.Event) iter.Seq[subscription] {
|
||||||
|
return func(yield func(subscription) bool) {
|
||||||
|
authorSubs, hasAuthorSubs := d.byAuthor[event.PubKey]
|
||||||
|
kindSubs, hasKindSubs := d.byKind[event.Kind]
|
||||||
|
|
||||||
|
if hasAuthorSubs && hasKindSubs {
|
||||||
|
for _, ssid := range authorSubs.Slice() {
|
||||||
|
sub, _ := d.subscriptions[ssid]
|
||||||
|
|
||||||
|
if kindSubs.Has(ssid) {
|
||||||
|
if filterMatchesTimestampConstraintsAndTags(sub.filter, event) {
|
||||||
|
if !yield(sub) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// matched author but not tags, so this event doesn't qualify for any filter
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if hasAuthorSubs {
|
||||||
|
for _, ssid := range authorSubs.Slice() {
|
||||||
|
sub, _ := d.subscriptions[ssid]
|
||||||
|
if sub.filter.Kinds != nil {
|
||||||
|
// if there are any kinds in the filter we already know this doesn't qualify
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if filterMatchesTimestampConstraintsAndTags(sub.filter, event) {
|
||||||
|
if !yield(sub) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if hasKindSubs {
|
||||||
|
for _, ssid := range kindSubs.Slice() {
|
||||||
|
sub, _ := d.subscriptions[ssid]
|
||||||
|
if sub.filter.Authors != nil {
|
||||||
|
// if there are any authors in the filter we already know this doesn't qualify
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if filterMatchesTimestampConstraintsAndTags(sub.filter, event) {
|
||||||
|
if !yield(sub) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ssid := range d.fallback.Slice() {
|
||||||
|
sub, _ := d.subscriptions[ssid]
|
||||||
|
if filterMatchesTimestampConstraintsAndTags(sub.filter, event) {
|
||||||
|
if !yield(sub) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:inline
|
||||||
|
func filterMatchesTimestampConstraintsAndTags(filter nostr.Filter, event nostr.Event) bool {
|
||||||
|
if filter.Since != 0 && event.CreatedAt < filter.Since {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if filter.Until != 0 && event.CreatedAt > filter.Until {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for f, v := range filter.Tags {
|
||||||
|
if !event.Tags.ContainsAny(f, v) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:inline
|
||||||
|
func tagKeyValueKey(tagKey, tagValue string) string {
|
||||||
|
return tagKey + "\x00" + tagValue
|
||||||
|
}
|
||||||
|
|
||||||
func (rl *Relay) GetListeningFilters() []nostr.Filter {
|
func (rl *Relay) GetListeningFilters() []nostr.Filter {
|
||||||
respfilters := make([]nostr.Filter, len(rl.listeners))
|
respfilters := make([]nostr.Filter, 0, len(rl.dispatcher.subscriptions))
|
||||||
for i, l := range rl.listeners {
|
for _, sub := range rl.dispatcher.subscriptions {
|
||||||
respfilters[i] = l.filter
|
respfilters = append(respfilters, sub.filter)
|
||||||
}
|
}
|
||||||
return respfilters
|
return respfilters
|
||||||
}
|
}
|
||||||
@@ -36,7 +223,6 @@ func (rl *Relay) GetListeningFilters() []nostr.Filter {
|
|||||||
func (rl *Relay) addListener(
|
func (rl *Relay) addListener(
|
||||||
ws *WebSocket,
|
ws *WebSocket,
|
||||||
id string,
|
id string,
|
||||||
subrelay *Relay,
|
|
||||||
filter nostr.Filter,
|
filter nostr.Filter,
|
||||||
cancel context.CancelCauseFunc,
|
cancel context.CancelCauseFunc,
|
||||||
) {
|
) {
|
||||||
@@ -48,18 +234,16 @@ func (rl *Relay) addListener(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if specs, ok := rl.clients[ws]; ok /* this will always be true unless client has disconnected very rapidly */ {
|
if specs, ok := rl.clients[ws]; ok /* this will always be true unless client has disconnected very rapidly */ {
|
||||||
idx := len(subrelay.listeners)
|
ssid := rl.dispatcher.addSubscription(subscription{
|
||||||
rl.clients[ws] = append(specs, listenerSpec{
|
|
||||||
id: id,
|
|
||||||
cancel: cancel,
|
|
||||||
subrelay: subrelay,
|
|
||||||
index: idx,
|
|
||||||
})
|
|
||||||
subrelay.listeners = append(subrelay.listeners, listener{
|
|
||||||
ws: ws,
|
ws: ws,
|
||||||
id: id,
|
id: id,
|
||||||
filter: filter,
|
filter: filter,
|
||||||
})
|
})
|
||||||
|
rl.clients[ws] = append(specs, listenerSpec{
|
||||||
|
ssid: ssid,
|
||||||
|
cancel: cancel,
|
||||||
|
sid: id,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,35 +254,16 @@ func (rl *Relay) removeListenerId(ws *WebSocket, id string) {
|
|||||||
defer rl.clientsMutex.Unlock()
|
defer rl.clientsMutex.Unlock()
|
||||||
|
|
||||||
if specs, ok := rl.clients[ws]; ok {
|
if specs, ok := rl.clients[ws]; ok {
|
||||||
// swap delete specs that match this id
|
kept := specs[:0]
|
||||||
for s := len(specs) - 1; s >= 0; s-- {
|
for _, spec := range specs {
|
||||||
spec := specs[s]
|
if spec.sid == id {
|
||||||
if spec.id == id {
|
|
||||||
spec.cancel(ErrSubscriptionClosedByClient)
|
spec.cancel(ErrSubscriptionClosedByClient)
|
||||||
specs[s] = specs[len(specs)-1]
|
rl.dispatcher.removeSubscription(spec.ssid)
|
||||||
specs = specs[0 : len(specs)-1]
|
continue
|
||||||
rl.clients[ws] = specs
|
|
||||||
|
|
||||||
// swap delete listeners one at a time, as they may be each in a different subrelay
|
|
||||||
srl := spec.subrelay // == rl in normal cases, but different when this came from a route
|
|
||||||
|
|
||||||
if spec.index != len(srl.listeners)-1 {
|
|
||||||
movedFromIndex := len(srl.listeners) - 1
|
|
||||||
moved := srl.listeners[movedFromIndex] // this wasn't removed, but will be moved
|
|
||||||
srl.listeners[spec.index] = moved
|
|
||||||
|
|
||||||
// now we must update the the listener we just moved
|
|
||||||
// so its .index reflects its new position on srl.listeners
|
|
||||||
movedSpecs := rl.clients[moved.ws]
|
|
||||||
idx := slices.IndexFunc(movedSpecs, func(ls listenerSpec) bool {
|
|
||||||
return ls.index == movedFromIndex && ls.subrelay == srl
|
|
||||||
})
|
|
||||||
movedSpecs[idx].index = spec.index
|
|
||||||
rl.clients[moved.ws] = movedSpecs
|
|
||||||
}
|
|
||||||
srl.listeners = srl.listeners[0 : len(srl.listeners)-1] // finally reduce the slice length
|
|
||||||
}
|
}
|
||||||
|
kept = append(kept, spec)
|
||||||
}
|
}
|
||||||
|
rl.clients[ws] = kept
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,31 +271,9 @@ func (rl *Relay) removeClientAndListeners(ws *WebSocket) {
|
|||||||
rl.clientsMutex.Lock()
|
rl.clientsMutex.Lock()
|
||||||
defer rl.clientsMutex.Unlock()
|
defer rl.clientsMutex.Unlock()
|
||||||
if specs, ok := rl.clients[ws]; ok {
|
if specs, ok := rl.clients[ws]; ok {
|
||||||
// swap delete listeners and delete client (all specs will be deleted)
|
for _, spec := range specs {
|
||||||
for s, spec := range specs {
|
|
||||||
// no need to cancel contexts since they inherit from the main connection context
|
// no need to cancel contexts since they inherit from the main connection context
|
||||||
// just delete the listeners (swap-delete)
|
rl.dispatcher.removeSubscription(spec.ssid)
|
||||||
srl := spec.subrelay
|
|
||||||
|
|
||||||
if spec.index != len(srl.listeners)-1 {
|
|
||||||
movedFromIndex := len(srl.listeners) - 1
|
|
||||||
moved := srl.listeners[movedFromIndex] // this wasn't removed, but will be moved
|
|
||||||
srl.listeners[spec.index] = moved
|
|
||||||
|
|
||||||
// temporarily update the spec of the listener being removed to have index == -1
|
|
||||||
// (since it was removed) so it doesn't match in the search below
|
|
||||||
rl.clients[ws][s].index = -1
|
|
||||||
|
|
||||||
// now we must update the the listener we just moved
|
|
||||||
// so its .index reflects its new position on srl.listeners
|
|
||||||
movedSpecs := rl.clients[moved.ws]
|
|
||||||
idx := slices.IndexFunc(movedSpecs, func(ls listenerSpec) bool {
|
|
||||||
return ls.index == movedFromIndex && ls.subrelay == srl
|
|
||||||
})
|
|
||||||
movedSpecs[idx].index = spec.index
|
|
||||||
rl.clients[moved.ws] = movedSpecs
|
|
||||||
}
|
|
||||||
srl.listeners = srl.listeners[0 : len(srl.listeners)-1] // finally reduce the slice length
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
delete(rl.clients, ws)
|
delete(rl.clients, ws)
|
||||||
@@ -140,16 +283,14 @@ func (rl *Relay) removeClientAndListeners(ws *WebSocket) {
|
|||||||
func (rl *Relay) notifyListeners(event nostr.Event, skipPrevent bool) int {
|
func (rl *Relay) notifyListeners(event nostr.Event, skipPrevent bool) int {
|
||||||
count := 0
|
count := 0
|
||||||
listenersloop:
|
listenersloop:
|
||||||
for _, listener := range rl.listeners {
|
for sub := range rl.dispatcher.candidates(event) {
|
||||||
if listener.filter.Matches(event) {
|
if !skipPrevent && nil != rl.PreventBroadcast {
|
||||||
if !skipPrevent && nil != rl.PreventBroadcast {
|
if rl.PreventBroadcast(sub.ws, sub.filter, event) {
|
||||||
if rl.PreventBroadcast(listener.ws, listener.filter, event) {
|
continue listenersloop
|
||||||
continue listenersloop
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
listener.ws.WriteJSON(nostr.EventEnvelope{SubscriptionID: &listener.id, Event: event})
|
|
||||||
count++
|
|
||||||
}
|
}
|
||||||
|
sub.ws.WriteJSON(nostr.EventEnvelope{SubscriptionID: &sub.id, Event: event})
|
||||||
|
count++
|
||||||
}
|
}
|
||||||
return count
|
return count
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ func FuzzRandomListenerClientRemoving(f *testing.F) {
|
|||||||
l := 0
|
l := 0
|
||||||
|
|
||||||
for i := 0; i < totalWebsockets; i++ {
|
for i := 0; i < totalWebsockets; i++ {
|
||||||
ws := &WebSocket{}
|
ws := &WebSocket{Context: rl.ctx}
|
||||||
websockets = append(websockets, ws)
|
websockets = append(websockets, ws)
|
||||||
rl.clients[ws] = nil
|
rl.clients[ws] = nil
|
||||||
}
|
}
|
||||||
@@ -38,7 +38,7 @@ func FuzzRandomListenerClientRemoving(f *testing.F) {
|
|||||||
|
|
||||||
if s%addListenerFreq == 0 {
|
if s%addListenerFreq == 0 {
|
||||||
l++
|
l++
|
||||||
rl.addListener(ws, w+":"+idFromSeqLower(j), rl, f, cancel)
|
rl.addListener(ws, w+":"+idFromSeqLower(j), f, cancel)
|
||||||
}
|
}
|
||||||
|
|
||||||
s++
|
s++
|
||||||
@@ -46,14 +46,22 @@ func FuzzRandomListenerClientRemoving(f *testing.F) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
require.Len(t, rl.clients, totalWebsockets)
|
require.Len(t, rl.clients, totalWebsockets)
|
||||||
require.Len(t, rl.listeners, l)
|
ssidCount := 0
|
||||||
|
for _, specs := range rl.clients {
|
||||||
|
ssidCount += len(specs)
|
||||||
|
}
|
||||||
|
require.Equal(t, l, ssidCount)
|
||||||
|
|
||||||
for ws := range rl.clients {
|
for ws := range rl.clients {
|
||||||
rl.removeClientAndListeners(ws)
|
rl.removeClientAndListeners(ws)
|
||||||
}
|
}
|
||||||
|
|
||||||
require.Len(t, rl.clients, 0)
|
require.Len(t, rl.clients, 0)
|
||||||
require.Len(t, rl.listeners, 0)
|
ssidCount = 0
|
||||||
|
for _, specs := range rl.clients {
|
||||||
|
ssidCount += len(specs)
|
||||||
|
}
|
||||||
|
require.Equal(t, 0, ssidCount)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,7 +92,7 @@ func FuzzRandomListenerIdRemoving(f *testing.F) {
|
|||||||
extra := 0
|
extra := 0
|
||||||
|
|
||||||
for i := 0; i < totalWebsockets; i++ {
|
for i := 0; i < totalWebsockets; i++ {
|
||||||
ws := &WebSocket{}
|
ws := &WebSocket{Context: rl.ctx}
|
||||||
websockets = append(websockets, ws)
|
websockets = append(websockets, ws)
|
||||||
rl.clients[ws] = nil
|
rl.clients[ws] = nil
|
||||||
}
|
}
|
||||||
@@ -97,11 +105,11 @@ func FuzzRandomListenerIdRemoving(f *testing.F) {
|
|||||||
|
|
||||||
if s%addListenerFreq == 0 {
|
if s%addListenerFreq == 0 {
|
||||||
id := w + ":" + idFromSeqLower(j)
|
id := w + ":" + idFromSeqLower(j)
|
||||||
rl.addListener(ws, id, rl, f, cancel)
|
rl.addListener(ws, id, f, cancel)
|
||||||
subs = append(subs, wsid{ws, id})
|
subs = append(subs, wsid{ws, id})
|
||||||
|
|
||||||
if s%addExtraListenerFreq == 0 {
|
if s%addExtraListenerFreq == 0 {
|
||||||
rl.addListener(ws, id, rl, f, cancel)
|
rl.addListener(ws, id, f, cancel)
|
||||||
extra++
|
extra++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -111,7 +119,11 @@ func FuzzRandomListenerIdRemoving(f *testing.F) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
require.Len(t, rl.clients, totalWebsockets)
|
require.Len(t, rl.clients, totalWebsockets)
|
||||||
require.Len(t, rl.listeners, len(subs)+extra)
|
ssidCount := 0
|
||||||
|
for _, specs := range rl.clients {
|
||||||
|
ssidCount += len(specs)
|
||||||
|
}
|
||||||
|
require.Equal(t, len(subs)+extra, ssidCount)
|
||||||
|
|
||||||
rand.Shuffle(len(subs), func(i, j int) {
|
rand.Shuffle(len(subs), func(i, j int) {
|
||||||
subs[i], subs[j] = subs[j], subs[i]
|
subs[i], subs[j] = subs[j], subs[i]
|
||||||
@@ -120,7 +132,11 @@ func FuzzRandomListenerIdRemoving(f *testing.F) {
|
|||||||
rl.removeListenerId(wsidToRemove.ws, wsidToRemove.id)
|
rl.removeListenerId(wsidToRemove.ws, wsidToRemove.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
require.Len(t, rl.listeners, 0)
|
ssidCount = 0
|
||||||
|
for _, specs := range rl.clients {
|
||||||
|
ssidCount += len(specs)
|
||||||
|
}
|
||||||
|
require.Equal(t, 0, ssidCount)
|
||||||
require.Len(t, rl.clients, totalWebsockets)
|
require.Len(t, rl.clients, totalWebsockets)
|
||||||
for _, specs := range rl.clients {
|
for _, specs := range rl.clients {
|
||||||
require.Len(t, specs, 0)
|
require.Len(t, specs, 0)
|
||||||
@@ -129,23 +145,17 @@ func FuzzRandomListenerIdRemoving(f *testing.F) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func FuzzRouterListenersPabloCrash(f *testing.F) {
|
func FuzzRouterListenersPabloCrash(f *testing.F) {
|
||||||
f.Add(uint(3), uint(6), uint(2), uint(20))
|
f.Add(uint(6), uint(2), uint(20))
|
||||||
f.Fuzz(func(t *testing.T, totalRelays uint, totalConns uint, subFreq uint, subIterations uint) {
|
f.Fuzz(func(t *testing.T, totalConns uint, subFreq uint, subIterations uint) {
|
||||||
totalRelays++
|
|
||||||
totalConns++
|
totalConns++
|
||||||
subFreq++
|
subFreq++
|
||||||
subIterations++
|
subIterations++
|
||||||
|
|
||||||
rl := NewRelay()
|
rl := NewRelay()
|
||||||
|
|
||||||
relays := make([]*Relay, int(totalRelays))
|
|
||||||
for i := 0; i < int(totalRelays); i++ {
|
|
||||||
relays[i] = NewRelay()
|
|
||||||
}
|
|
||||||
|
|
||||||
conns := make([]*WebSocket, int(totalConns))
|
conns := make([]*WebSocket, int(totalConns))
|
||||||
for i := 0; i < int(totalConns); i++ {
|
for i := 0; i < int(totalConns); i++ {
|
||||||
ws := &WebSocket{}
|
ws := &WebSocket{Context: rl.ctx}
|
||||||
conns[i] = ws
|
conns[i] = ws
|
||||||
rl.clients[ws] = make([]listenerSpec, 0, subIterations)
|
rl.clients[ws] = make([]listenerSpec, 0, subIterations)
|
||||||
}
|
}
|
||||||
@@ -159,18 +169,16 @@ func FuzzRouterListenersPabloCrash(f *testing.F) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
s := 0
|
s := 0
|
||||||
subs := make([]wsid, 0, subIterations*totalConns*totalRelays)
|
subs := make([]wsid, 0, subIterations*totalConns)
|
||||||
for i, conn := range conns {
|
for i, conn := range conns {
|
||||||
w := idFromSeqUpper(i)
|
w := idFromSeqUpper(i)
|
||||||
for j := 0; j < int(subIterations); j++ {
|
for j := 0; j < int(subIterations); j++ {
|
||||||
id := w + ":" + idFromSeqLower(j)
|
id := w + ":" + idFromSeqLower(j)
|
||||||
for _, rlt := range relays {
|
if s%int(subFreq) == 0 {
|
||||||
if s%int(subFreq) == 0 {
|
rl.addListener(conn, id, f, cancel)
|
||||||
rl.addListener(conn, id, rlt, f, cancel)
|
subs = append(subs, wsid{conn, id})
|
||||||
subs = append(subs, wsid{conn, id})
|
|
||||||
}
|
|
||||||
s++
|
|
||||||
}
|
}
|
||||||
|
s++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,8 +189,5 @@ func FuzzRouterListenersPabloCrash(f *testing.F) {
|
|||||||
for _, wsid := range subs {
|
for _, wsid := range subs {
|
||||||
require.Len(t, rl.clients[wsid.ws], 0)
|
require.Len(t, rl.clients[wsid.ws], 0)
|
||||||
}
|
}
|
||||||
for _, rlt := range relays {
|
|
||||||
require.Len(t, rlt.listeners, 0)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+117
-225
@@ -26,8 +26,8 @@ func idFromSeq(seq int, min, max int) string {
|
|||||||
func TestListenerSetupAndRemoveOnce(t *testing.T) {
|
func TestListenerSetupAndRemoveOnce(t *testing.T) {
|
||||||
rl := NewRelay()
|
rl := NewRelay()
|
||||||
|
|
||||||
ws1 := &WebSocket{}
|
ws1 := &WebSocket{Context: rl.ctx}
|
||||||
ws2 := &WebSocket{}
|
ws2 := &WebSocket{Context: rl.ctx}
|
||||||
|
|
||||||
f1 := nostr.Filter{Kinds: []nostr.Kind{1}}
|
f1 := nostr.Filter{Kinds: []nostr.Kind{1}}
|
||||||
f2 := nostr.Filter{Kinds: []nostr.Kind{2}}
|
f2 := nostr.Filter{Kinds: []nostr.Kind{2}}
|
||||||
@@ -39,28 +39,21 @@ func TestListenerSetupAndRemoveOnce(t *testing.T) {
|
|||||||
var cancel func(cause error) = nil
|
var cancel func(cause error) = nil
|
||||||
|
|
||||||
t.Run("adding listeners", func(t *testing.T) {
|
t.Run("adding listeners", func(t *testing.T) {
|
||||||
rl.addListener(ws1, "1a", rl, f1, cancel)
|
rl.addListener(ws1, "1a", f1, cancel)
|
||||||
rl.addListener(ws1, "1b", rl, f2, cancel)
|
rl.addListener(ws1, "1b", f2, cancel)
|
||||||
rl.addListener(ws2, "2a", rl, f3, cancel)
|
rl.addListener(ws2, "2a", f3, cancel)
|
||||||
rl.addListener(ws1, "1c", rl, f3, cancel)
|
rl.addListener(ws1, "1c", f3, cancel)
|
||||||
|
|
||||||
require.Equal(t, map[*WebSocket][]listenerSpec{
|
require.Equal(t, map[*WebSocket][]listenerSpec{
|
||||||
ws1: {
|
ws1: {
|
||||||
{"1a", cancel, 0, rl},
|
{1, "1a", cancel},
|
||||||
{"1b", cancel, 1, rl},
|
{2, "1b", cancel},
|
||||||
{"1c", cancel, 3, rl},
|
{4, "1c", cancel},
|
||||||
},
|
},
|
||||||
ws2: {
|
ws2: {
|
||||||
{"2a", cancel, 2, rl},
|
{3, "2a", cancel},
|
||||||
},
|
},
|
||||||
}, rl.clients)
|
}, rl.clients)
|
||||||
|
|
||||||
require.Equal(t, []listener{
|
|
||||||
{"1a", f1, ws1},
|
|
||||||
{"1b", f2, ws1},
|
|
||||||
{"2a", f3, ws2},
|
|
||||||
{"1c", f3, ws1},
|
|
||||||
}, rl.listeners)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("removing a client", func(t *testing.T) {
|
t.Run("removing a client", func(t *testing.T) {
|
||||||
@@ -68,23 +61,19 @@ func TestListenerSetupAndRemoveOnce(t *testing.T) {
|
|||||||
|
|
||||||
require.Equal(t, map[*WebSocket][]listenerSpec{
|
require.Equal(t, map[*WebSocket][]listenerSpec{
|
||||||
ws2: {
|
ws2: {
|
||||||
{"2a", cancel, 0, rl},
|
{3, "2a", cancel},
|
||||||
},
|
},
|
||||||
}, rl.clients)
|
}, rl.clients)
|
||||||
|
|
||||||
require.Equal(t, []listener{
|
|
||||||
{"2a", f3, ws2},
|
|
||||||
}, rl.listeners)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestListenerMoreConvolutedCase(t *testing.T) {
|
func TestListenerMoreConvolutedCase(t *testing.T) {
|
||||||
rl := NewRelay()
|
rl := NewRelay()
|
||||||
|
|
||||||
ws1 := &WebSocket{}
|
ws1 := &WebSocket{Context: rl.ctx}
|
||||||
ws2 := &WebSocket{}
|
ws2 := &WebSocket{Context: rl.ctx}
|
||||||
ws3 := &WebSocket{}
|
ws3 := &WebSocket{Context: rl.ctx}
|
||||||
ws4 := &WebSocket{}
|
ws4 := &WebSocket{Context: rl.ctx}
|
||||||
|
|
||||||
f1 := nostr.Filter{Kinds: []nostr.Kind{1}}
|
f1 := nostr.Filter{Kinds: []nostr.Kind{1}}
|
||||||
f2 := nostr.Filter{Kinds: []nostr.Kind{2}}
|
f2 := nostr.Filter{Kinds: []nostr.Kind{2}}
|
||||||
@@ -98,35 +87,27 @@ func TestListenerMoreConvolutedCase(t *testing.T) {
|
|||||||
var cancel func(cause error) = nil
|
var cancel func(cause error) = nil
|
||||||
|
|
||||||
t.Run("adding listeners", func(t *testing.T) {
|
t.Run("adding listeners", func(t *testing.T) {
|
||||||
rl.addListener(ws1, "c", rl, f1, cancel)
|
rl.addListener(ws1, "c", f1, cancel)
|
||||||
rl.addListener(ws2, "b", rl, f2, cancel)
|
rl.addListener(ws2, "b", f2, cancel)
|
||||||
rl.addListener(ws3, "a", rl, f3, cancel)
|
rl.addListener(ws3, "a", f3, cancel)
|
||||||
rl.addListener(ws4, "d", rl, f3, cancel)
|
rl.addListener(ws4, "d", f3, cancel)
|
||||||
rl.addListener(ws2, "b", rl, f1, cancel)
|
rl.addListener(ws2, "b", f1, cancel)
|
||||||
|
|
||||||
require.Equal(t, map[*WebSocket][]listenerSpec{
|
require.Equal(t, map[*WebSocket][]listenerSpec{
|
||||||
ws1: {
|
ws1: {
|
||||||
{"c", cancel, 0, rl},
|
{1, "c", cancel},
|
||||||
},
|
},
|
||||||
ws2: {
|
ws2: {
|
||||||
{"b", cancel, 1, rl},
|
{2, "b", cancel},
|
||||||
{"b", cancel, 4, rl},
|
{5, "b", cancel},
|
||||||
},
|
},
|
||||||
ws3: {
|
ws3: {
|
||||||
{"a", cancel, 2, rl},
|
{3, "a", cancel},
|
||||||
},
|
},
|
||||||
ws4: {
|
ws4: {
|
||||||
{"d", cancel, 3, rl},
|
{4, "d", cancel},
|
||||||
},
|
},
|
||||||
}, rl.clients)
|
}, rl.clients)
|
||||||
|
|
||||||
require.Equal(t, []listener{
|
|
||||||
{"c", f1, ws1},
|
|
||||||
{"b", f2, ws2},
|
|
||||||
{"a", f3, ws3},
|
|
||||||
{"d", f3, ws4},
|
|
||||||
{"b", f1, ws2},
|
|
||||||
}, rl.listeners)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("removing a client", func(t *testing.T) {
|
t.Run("removing a client", func(t *testing.T) {
|
||||||
@@ -134,85 +115,62 @@ func TestListenerMoreConvolutedCase(t *testing.T) {
|
|||||||
|
|
||||||
require.Equal(t, map[*WebSocket][]listenerSpec{
|
require.Equal(t, map[*WebSocket][]listenerSpec{
|
||||||
ws1: {
|
ws1: {
|
||||||
{"c", cancel, 0, rl},
|
{1, "c", cancel},
|
||||||
},
|
},
|
||||||
ws3: {
|
ws3: {
|
||||||
{"a", cancel, 2, rl},
|
{3, "a", cancel},
|
||||||
},
|
},
|
||||||
ws4: {
|
ws4: {
|
||||||
{"d", cancel, 1, rl},
|
{4, "d", cancel},
|
||||||
},
|
},
|
||||||
}, rl.clients)
|
}, rl.clients)
|
||||||
|
|
||||||
require.Equal(t, []listener{
|
|
||||||
{"c", f1, ws1},
|
|
||||||
{"d", f3, ws4},
|
|
||||||
{"a", f3, ws3},
|
|
||||||
}, rl.listeners)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("reorganize the first case differently and then remove again", func(t *testing.T) {
|
t.Run("reorganize the first case differently and then remove again", func(t *testing.T) {
|
||||||
rl.clients = map[*WebSocket][]listenerSpec{
|
rl.clients = map[*WebSocket][]listenerSpec{
|
||||||
ws1: {
|
ws1: {
|
||||||
{"c", cancel, 1, rl},
|
{2, "c", cancel},
|
||||||
},
|
},
|
||||||
ws2: {
|
ws2: {
|
||||||
{"b", cancel, 2, rl},
|
{3, "b", cancel},
|
||||||
{"b", cancel, 4, rl},
|
{5, "b", cancel},
|
||||||
},
|
},
|
||||||
ws3: {
|
ws3: {
|
||||||
{"a", cancel, 0, rl},
|
{1, "a", cancel},
|
||||||
},
|
},
|
||||||
ws4: {
|
ws4: {
|
||||||
{"d", cancel, 3, rl},
|
{4, "d", cancel},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
rl.listeners = []listener{
|
|
||||||
{"a", f3, ws3},
|
|
||||||
{"c", f1, ws1},
|
|
||||||
{"b", f2, ws2},
|
|
||||||
{"d", f3, ws4},
|
|
||||||
{"b", f1, ws2},
|
|
||||||
}
|
|
||||||
|
|
||||||
rl.removeClientAndListeners(ws2)
|
rl.removeClientAndListeners(ws2)
|
||||||
|
|
||||||
require.Equal(t, map[*WebSocket][]listenerSpec{
|
require.Equal(t, map[*WebSocket][]listenerSpec{
|
||||||
ws1: {
|
ws1: {
|
||||||
{"c", cancel, 1, rl},
|
{2, "c", cancel},
|
||||||
},
|
},
|
||||||
ws3: {
|
ws3: {
|
||||||
{"a", cancel, 0, rl},
|
{1, "a", cancel},
|
||||||
},
|
},
|
||||||
ws4: {
|
ws4: {
|
||||||
{"d", cancel, 2, rl},
|
{4, "d", cancel},
|
||||||
},
|
},
|
||||||
}, rl.clients)
|
}, rl.clients)
|
||||||
|
|
||||||
require.Equal(t, []listener{
|
|
||||||
{"a", f3, ws3},
|
|
||||||
{"c", f1, ws1},
|
|
||||||
{"d", f3, ws4},
|
|
||||||
}, rl.listeners)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestListenerMoreStuffWithMultipleRelays(t *testing.T) {
|
func TestListenerMoreStuffWithMultipleRelays(t *testing.T) {
|
||||||
rl := NewRelay()
|
rl := NewRelay()
|
||||||
|
|
||||||
ws1 := &WebSocket{}
|
ws1 := &WebSocket{Context: rl.ctx}
|
||||||
ws2 := &WebSocket{}
|
ws2 := &WebSocket{Context: rl.ctx}
|
||||||
ws3 := &WebSocket{}
|
ws3 := &WebSocket{Context: rl.ctx}
|
||||||
ws4 := &WebSocket{}
|
ws4 := &WebSocket{Context: rl.ctx}
|
||||||
|
|
||||||
f1 := nostr.Filter{Kinds: []nostr.Kind{1}}
|
f1 := nostr.Filter{Kinds: []nostr.Kind{1}}
|
||||||
f2 := nostr.Filter{Kinds: []nostr.Kind{2}}
|
f2 := nostr.Filter{Kinds: []nostr.Kind{2}}
|
||||||
f3 := nostr.Filter{Kinds: []nostr.Kind{3}}
|
f3 := nostr.Filter{Kinds: []nostr.Kind{3}}
|
||||||
|
|
||||||
rlx := NewRelay()
|
|
||||||
rly := NewRelay()
|
|
||||||
rlz := NewRelay()
|
|
||||||
|
|
||||||
rl.clients[ws1] = nil
|
rl.clients[ws1] = nil
|
||||||
rl.clients[ws2] = nil
|
rl.clients[ws2] = nil
|
||||||
rl.clients[ws3] = nil
|
rl.clients[ws3] = nil
|
||||||
@@ -221,56 +179,37 @@ func TestListenerMoreStuffWithMultipleRelays(t *testing.T) {
|
|||||||
var cancel func(cause error) = nil
|
var cancel func(cause error) = nil
|
||||||
|
|
||||||
t.Run("adding listeners", func(t *testing.T) {
|
t.Run("adding listeners", func(t *testing.T) {
|
||||||
rl.addListener(ws1, "c", rlx, f1, cancel)
|
rl.addListener(ws1, "c", f1, cancel)
|
||||||
rl.addListener(ws2, "b", rly, f2, cancel)
|
rl.addListener(ws2, "b", f2, cancel)
|
||||||
rl.addListener(ws3, "a", rlz, f3, cancel)
|
rl.addListener(ws3, "a", f3, cancel)
|
||||||
rl.addListener(ws4, "d", rlx, f3, cancel)
|
rl.addListener(ws4, "d", f3, cancel)
|
||||||
rl.addListener(ws4, "e", rlx, f3, cancel)
|
rl.addListener(ws4, "e", f3, cancel)
|
||||||
rl.addListener(ws3, "a", rlx, f3, cancel)
|
rl.addListener(ws3, "a", f3, cancel)
|
||||||
rl.addListener(ws4, "e", rly, f3, cancel)
|
rl.addListener(ws4, "e", f3, cancel)
|
||||||
rl.addListener(ws3, "f", rly, f3, cancel)
|
rl.addListener(ws3, "f", f3, cancel)
|
||||||
rl.addListener(ws1, "g", rlz, f1, cancel)
|
rl.addListener(ws1, "g", f1, cancel)
|
||||||
rl.addListener(ws2, "g", rlz, f2, cancel)
|
rl.addListener(ws2, "g", f2, cancel)
|
||||||
|
|
||||||
require.Equal(t, map[*WebSocket][]listenerSpec{
|
require.Equal(t, map[*WebSocket][]listenerSpec{
|
||||||
ws1: {
|
ws1: {
|
||||||
{"c", cancel, 0, rlx},
|
{1, "c", cancel},
|
||||||
{"g", cancel, 1, rlz},
|
{9, "g", cancel},
|
||||||
},
|
},
|
||||||
ws2: {
|
ws2: {
|
||||||
{"b", cancel, 0, rly},
|
{2, "b", cancel},
|
||||||
{"g", cancel, 2, rlz},
|
{10, "g", cancel},
|
||||||
},
|
},
|
||||||
ws3: {
|
ws3: {
|
||||||
{"a", cancel, 0, rlz},
|
{3, "a", cancel},
|
||||||
{"a", cancel, 3, rlx},
|
{6, "a", cancel},
|
||||||
{"f", cancel, 2, rly},
|
{8, "f", cancel},
|
||||||
},
|
},
|
||||||
ws4: {
|
ws4: {
|
||||||
{"d", cancel, 1, rlx},
|
{4, "d", cancel},
|
||||||
{"e", cancel, 2, rlx},
|
{5, "e", cancel},
|
||||||
{"e", cancel, 1, rly},
|
{7, "e", cancel},
|
||||||
},
|
},
|
||||||
}, rl.clients)
|
}, rl.clients)
|
||||||
|
|
||||||
require.Equal(t, []listener{
|
|
||||||
{"c", f1, ws1},
|
|
||||||
{"d", f3, ws4},
|
|
||||||
{"e", f3, ws4},
|
|
||||||
{"a", f3, ws3},
|
|
||||||
}, rlx.listeners)
|
|
||||||
|
|
||||||
require.Equal(t, []listener{
|
|
||||||
{"b", f2, ws2},
|
|
||||||
{"e", f3, ws4},
|
|
||||||
{"f", f3, ws3},
|
|
||||||
}, rly.listeners)
|
|
||||||
|
|
||||||
require.Equal(t, []listener{
|
|
||||||
{"a", f3, ws3},
|
|
||||||
{"g", f1, ws1},
|
|
||||||
{"g", f2, ws2},
|
|
||||||
}, rlz.listeners)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("removing a subscription id", func(t *testing.T) {
|
t.Run("removing a subscription id", func(t *testing.T) {
|
||||||
@@ -280,41 +219,23 @@ func TestListenerMoreStuffWithMultipleRelays(t *testing.T) {
|
|||||||
|
|
||||||
require.Equal(t, map[*WebSocket][]listenerSpec{
|
require.Equal(t, map[*WebSocket][]listenerSpec{
|
||||||
ws1: {
|
ws1: {
|
||||||
{"c", cancel, 0, rlx},
|
{1, "c", cancel},
|
||||||
{"g", cancel, 1, rlz},
|
{9, "g", cancel},
|
||||||
},
|
},
|
||||||
ws2: {
|
ws2: {
|
||||||
{"b", cancel, 0, rly},
|
{2, "b", cancel},
|
||||||
{"g", cancel, 2, rlz},
|
{10, "g", cancel},
|
||||||
},
|
},
|
||||||
ws3: {
|
ws3: {
|
||||||
{"a", cancel, 0, rlz},
|
{3, "a", cancel},
|
||||||
{"a", cancel, 1, rlx},
|
{6, "a", cancel},
|
||||||
{"f", cancel, 2, rly},
|
{8, "f", cancel},
|
||||||
},
|
},
|
||||||
ws4: {
|
ws4: {
|
||||||
{"e", cancel, 1, rly},
|
{5, "e", cancel},
|
||||||
{"e", cancel, 2, rlx},
|
{7, "e", cancel},
|
||||||
},
|
},
|
||||||
}, rl.clients)
|
}, rl.clients)
|
||||||
|
|
||||||
require.Equal(t, []listener{
|
|
||||||
{"c", f1, ws1},
|
|
||||||
{"a", f3, ws3},
|
|
||||||
{"e", f3, ws4},
|
|
||||||
}, rlx.listeners)
|
|
||||||
|
|
||||||
require.Equal(t, []listener{
|
|
||||||
{"b", f2, ws2},
|
|
||||||
{"e", f3, ws4},
|
|
||||||
{"f", f3, ws3},
|
|
||||||
}, rly.listeners)
|
|
||||||
|
|
||||||
require.Equal(t, []listener{
|
|
||||||
{"a", f3, ws3},
|
|
||||||
{"g", f1, ws1},
|
|
||||||
{"g", f2, ws2},
|
|
||||||
}, rlz.listeners)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("removing another subscription id", func(t *testing.T) {
|
t.Run("removing another subscription id", func(t *testing.T) {
|
||||||
@@ -325,37 +246,21 @@ func TestListenerMoreStuffWithMultipleRelays(t *testing.T) {
|
|||||||
|
|
||||||
require.Equal(t, map[*WebSocket][]listenerSpec{
|
require.Equal(t, map[*WebSocket][]listenerSpec{
|
||||||
ws1: {
|
ws1: {
|
||||||
{"c", cancel, 0, rlx},
|
{1, "c", cancel},
|
||||||
{"g", cancel, 1, rlz},
|
{9, "g", cancel},
|
||||||
},
|
},
|
||||||
ws2: {
|
ws2: {
|
||||||
{"b", cancel, 0, rly},
|
{2, "b", cancel},
|
||||||
{"g", cancel, 0, rlz},
|
{10, "g", cancel},
|
||||||
},
|
},
|
||||||
ws3: {
|
ws3: {
|
||||||
{"f", cancel, 2, rly},
|
{8, "f", cancel},
|
||||||
},
|
},
|
||||||
ws4: {
|
ws4: {
|
||||||
{"e", cancel, 1, rly},
|
{5, "e", cancel},
|
||||||
{"e", cancel, 1, rlx},
|
{7, "e", cancel},
|
||||||
},
|
},
|
||||||
}, rl.clients)
|
}, rl.clients)
|
||||||
|
|
||||||
require.Equal(t, []listener{
|
|
||||||
{"c", f1, ws1},
|
|
||||||
{"e", f3, ws4},
|
|
||||||
}, rlx.listeners)
|
|
||||||
|
|
||||||
require.Equal(t, []listener{
|
|
||||||
{"b", f2, ws2},
|
|
||||||
{"e", f3, ws4},
|
|
||||||
{"f", f3, ws3},
|
|
||||||
}, rly.listeners)
|
|
||||||
|
|
||||||
require.Equal(t, []listener{
|
|
||||||
{"g", f2, ws2},
|
|
||||||
{"g", f1, ws1},
|
|
||||||
}, rlz.listeners)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("removing a connection", func(t *testing.T) {
|
t.Run("removing a connection", func(t *testing.T) {
|
||||||
@@ -363,31 +268,17 @@ func TestListenerMoreStuffWithMultipleRelays(t *testing.T) {
|
|||||||
|
|
||||||
require.Equal(t, map[*WebSocket][]listenerSpec{
|
require.Equal(t, map[*WebSocket][]listenerSpec{
|
||||||
ws1: {
|
ws1: {
|
||||||
{"c", cancel, 0, rlx},
|
{1, "c", cancel},
|
||||||
{"g", cancel, 0, rlz},
|
{9, "g", cancel},
|
||||||
},
|
},
|
||||||
ws3: {
|
ws3: {
|
||||||
{"f", cancel, 0, rly},
|
{8, "f", cancel},
|
||||||
},
|
},
|
||||||
ws4: {
|
ws4: {
|
||||||
{"e", cancel, 1, rly},
|
{5, "e", cancel},
|
||||||
{"e", cancel, 1, rlx},
|
{7, "e", cancel},
|
||||||
},
|
},
|
||||||
}, rl.clients)
|
}, rl.clients)
|
||||||
|
|
||||||
require.Equal(t, []listener{
|
|
||||||
{"c", f1, ws1},
|
|
||||||
{"e", f3, ws4},
|
|
||||||
}, rlx.listeners)
|
|
||||||
|
|
||||||
require.Equal(t, []listener{
|
|
||||||
{"f", f3, ws3},
|
|
||||||
{"e", f3, ws4},
|
|
||||||
}, rly.listeners)
|
|
||||||
|
|
||||||
require.Equal(t, []listener{
|
|
||||||
{"g", f1, ws1},
|
|
||||||
}, rlz.listeners)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("removing another subscription id", func(t *testing.T) {
|
t.Run("removing another subscription id", func(t *testing.T) {
|
||||||
@@ -398,26 +289,14 @@ func TestListenerMoreStuffWithMultipleRelays(t *testing.T) {
|
|||||||
|
|
||||||
require.Equal(t, map[*WebSocket][]listenerSpec{
|
require.Equal(t, map[*WebSocket][]listenerSpec{
|
||||||
ws1: {
|
ws1: {
|
||||||
{"c", cancel, 0, rlx},
|
{1, "c", cancel},
|
||||||
{"g", cancel, 0, rlz},
|
{9, "g", cancel},
|
||||||
},
|
},
|
||||||
ws3: {
|
ws3: {
|
||||||
{"f", cancel, 0, rly},
|
{8, "f", cancel},
|
||||||
},
|
},
|
||||||
ws4: {},
|
ws4: {},
|
||||||
}, rl.clients)
|
}, rl.clients)
|
||||||
|
|
||||||
require.Equal(t, []listener{
|
|
||||||
{"c", f1, ws1},
|
|
||||||
}, rlx.listeners)
|
|
||||||
|
|
||||||
require.Equal(t, []listener{
|
|
||||||
{"f", f3, ws3},
|
|
||||||
}, rly.listeners)
|
|
||||||
|
|
||||||
require.Equal(t, []listener{
|
|
||||||
{"g", f1, ws1},
|
|
||||||
}, rlz.listeners)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -432,7 +311,7 @@ func TestRandomListenerClientRemoving(t *testing.T) {
|
|||||||
l := 0
|
l := 0
|
||||||
|
|
||||||
for i := 0; i < 20; i++ {
|
for i := 0; i < 20; i++ {
|
||||||
ws := &WebSocket{}
|
ws := &WebSocket{Context: rl.ctx}
|
||||||
websockets = append(websockets, ws)
|
websockets = append(websockets, ws)
|
||||||
rl.clients[ws] = nil
|
rl.clients[ws] = nil
|
||||||
}
|
}
|
||||||
@@ -444,20 +323,28 @@ func TestRandomListenerClientRemoving(t *testing.T) {
|
|||||||
|
|
||||||
if rand.Intn(2) < 1 {
|
if rand.Intn(2) < 1 {
|
||||||
l++
|
l++
|
||||||
rl.addListener(ws, w+":"+idFromSeqLower(j), rl, f, cancel)
|
rl.addListener(ws, w+":"+idFromSeqLower(j), f, cancel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
require.Len(t, rl.clients, 20)
|
require.Len(t, rl.clients, 20)
|
||||||
require.Len(t, rl.listeners, l)
|
ssidCount := 0
|
||||||
|
for _, specs := range rl.clients {
|
||||||
|
ssidCount += len(specs)
|
||||||
|
}
|
||||||
|
require.Equal(t, l, ssidCount)
|
||||||
|
|
||||||
for ws := range rl.clients {
|
for ws := range rl.clients {
|
||||||
rl.removeClientAndListeners(ws)
|
rl.removeClientAndListeners(ws)
|
||||||
}
|
}
|
||||||
|
|
||||||
require.Len(t, rl.clients, 0)
|
require.Len(t, rl.clients, 0)
|
||||||
require.Len(t, rl.listeners, 0)
|
ssidCount = 0
|
||||||
|
for _, specs := range rl.clients {
|
||||||
|
ssidCount += len(specs)
|
||||||
|
}
|
||||||
|
require.Equal(t, 0, ssidCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRandomListenerIdRemoving(t *testing.T) {
|
func TestRandomListenerIdRemoving(t *testing.T) {
|
||||||
@@ -477,7 +364,7 @@ func TestRandomListenerIdRemoving(t *testing.T) {
|
|||||||
extra := 0
|
extra := 0
|
||||||
|
|
||||||
for i := 0; i < 20; i++ {
|
for i := 0; i < 20; i++ {
|
||||||
ws := &WebSocket{}
|
ws := &WebSocket{Context: rl.ctx}
|
||||||
websockets = append(websockets, ws)
|
websockets = append(websockets, ws)
|
||||||
rl.clients[ws] = nil
|
rl.clients[ws] = nil
|
||||||
}
|
}
|
||||||
@@ -489,11 +376,11 @@ func TestRandomListenerIdRemoving(t *testing.T) {
|
|||||||
|
|
||||||
if rand.Intn(2) < 1 {
|
if rand.Intn(2) < 1 {
|
||||||
id := w + ":" + idFromSeqLower(j)
|
id := w + ":" + idFromSeqLower(j)
|
||||||
rl.addListener(ws, id, rl, f, cancel)
|
rl.addListener(ws, id, f, cancel)
|
||||||
subs = append(subs, wsid{ws, id})
|
subs = append(subs, wsid{ws, id})
|
||||||
|
|
||||||
if rand.Intn(5) < 1 {
|
if rand.Intn(5) < 1 {
|
||||||
rl.addListener(ws, id, rl, f, cancel)
|
rl.addListener(ws, id, f, cancel)
|
||||||
extra++
|
extra++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -501,7 +388,11 @@ func TestRandomListenerIdRemoving(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
require.Len(t, rl.clients, 20)
|
require.Len(t, rl.clients, 20)
|
||||||
require.Len(t, rl.listeners, len(subs)+extra)
|
ssidCount := 0
|
||||||
|
for _, specs := range rl.clients {
|
||||||
|
ssidCount += len(specs)
|
||||||
|
}
|
||||||
|
require.Equal(t, len(subs)+extra, ssidCount)
|
||||||
|
|
||||||
rand.Shuffle(len(subs), func(i, j int) {
|
rand.Shuffle(len(subs), func(i, j int) {
|
||||||
subs[i], subs[j] = subs[j], subs[i]
|
subs[i], subs[j] = subs[j], subs[i]
|
||||||
@@ -510,7 +401,11 @@ func TestRandomListenerIdRemoving(t *testing.T) {
|
|||||||
rl.removeListenerId(wsidToRemove.ws, wsidToRemove.id)
|
rl.removeListenerId(wsidToRemove.ws, wsidToRemove.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
require.Len(t, rl.listeners, 0)
|
ssidCount = 0
|
||||||
|
for _, specs := range rl.clients {
|
||||||
|
ssidCount += len(specs)
|
||||||
|
}
|
||||||
|
require.Equal(t, 0, ssidCount)
|
||||||
require.Len(t, rl.clients, 20)
|
require.Len(t, rl.clients, 20)
|
||||||
for _, specs := range rl.clients {
|
for _, specs := range rl.clients {
|
||||||
require.Len(t, specs, 0)
|
require.Len(t, specs, 0)
|
||||||
@@ -520,12 +415,9 @@ func TestRandomListenerIdRemoving(t *testing.T) {
|
|||||||
func TestRouterListenersPabloCrash(t *testing.T) {
|
func TestRouterListenersPabloCrash(t *testing.T) {
|
||||||
rl := NewRelay()
|
rl := NewRelay()
|
||||||
|
|
||||||
rla := NewRelay()
|
ws1 := &WebSocket{Context: rl.ctx}
|
||||||
rlb := NewRelay()
|
ws2 := &WebSocket{Context: rl.ctx}
|
||||||
|
ws3 := &WebSocket{Context: rl.ctx}
|
||||||
ws1 := &WebSocket{}
|
|
||||||
ws2 := &WebSocket{}
|
|
||||||
ws3 := &WebSocket{}
|
|
||||||
|
|
||||||
rl.clients[ws1] = nil
|
rl.clients[ws1] = nil
|
||||||
rl.clients[ws2] = nil
|
rl.clients[ws2] = nil
|
||||||
@@ -534,11 +426,11 @@ func TestRouterListenersPabloCrash(t *testing.T) {
|
|||||||
f := nostr.Filter{Kinds: []nostr.Kind{1}}
|
f := nostr.Filter{Kinds: []nostr.Kind{1}}
|
||||||
cancel := func(cause error) {}
|
cancel := func(cause error) {}
|
||||||
|
|
||||||
rl.addListener(ws1, ":1", rla, f, cancel)
|
rl.addListener(ws1, ":1", f, cancel)
|
||||||
rl.addListener(ws2, ":1", rlb, f, cancel)
|
rl.addListener(ws2, ":1", f, cancel)
|
||||||
rl.addListener(ws3, "a", rlb, f, cancel)
|
rl.addListener(ws3, "a", f, cancel)
|
||||||
rl.addListener(ws3, "b", rla, f, cancel)
|
rl.addListener(ws3, "b", f, cancel)
|
||||||
rl.addListener(ws3, "c", rlb, f, cancel)
|
rl.addListener(ws3, "c", f, cancel)
|
||||||
|
|
||||||
rl.removeClientAndListeners(ws1)
|
rl.removeClientAndListeners(ws1)
|
||||||
rl.removeClientAndListeners(ws3)
|
rl.removeClientAndListeners(ws3)
|
||||||
|
|||||||
@@ -124,6 +124,9 @@ var nostrReferencesPrefix = regexp.MustCompile(`\b(nevent1|npub1|nprofile1|note1
|
|||||||
|
|
||||||
func RejectUnprefixedNostrReferences(ctx context.Context, event nostr.Event) (bool, string) {
|
func RejectUnprefixedNostrReferences(ctx context.Context, event nostr.Event) (bool, string) {
|
||||||
content := sdk.GetMainContent(event)
|
content := sdk.GetMainContent(event)
|
||||||
|
if content == "" {
|
||||||
|
content = event.Content
|
||||||
|
}
|
||||||
|
|
||||||
// only do it for stuff that wasn't parsed as blocks already
|
// only do it for stuff that wasn't parsed as blocks already
|
||||||
// (since those are already good references or URLs)
|
// (since those are already good references or URLs)
|
||||||
|
|||||||
+2
-7
@@ -42,7 +42,7 @@ func NewRelay() *Relay {
|
|||||||
clients: make(map[*WebSocket][]listenerSpec, 100),
|
clients: make(map[*WebSocket][]listenerSpec, 100),
|
||||||
clientsMutex: channelmutex.New(),
|
clientsMutex: channelmutex.New(),
|
||||||
|
|
||||||
listeners: make([]listener, 0, 100),
|
dispatcher: newDispatcher(),
|
||||||
|
|
||||||
serveMux: &http.ServeMux{},
|
serveMux: &http.ServeMux{},
|
||||||
|
|
||||||
@@ -87,11 +87,6 @@ type Relay struct {
|
|||||||
// this can be ignored unless you know what you're doing
|
// this can be ignored unless you know what you're doing
|
||||||
ChallengePrefix string
|
ChallengePrefix string
|
||||||
|
|
||||||
// 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
|
// setting up handlers here will enable these methods
|
||||||
ManagementAPI RelayManagementAPI
|
ManagementAPI RelayManagementAPI
|
||||||
|
|
||||||
@@ -108,7 +103,7 @@ type Relay struct {
|
|||||||
// keep a connection reference to all connected clients for Server.Shutdown
|
// keep a connection reference to all connected clients for Server.Shutdown
|
||||||
// also used for keeping track of who is listening to what
|
// also used for keeping track of who is listening to what
|
||||||
clients map[*WebSocket][]listenerSpec
|
clients map[*WebSocket][]listenerSpec
|
||||||
listeners []listener
|
dispatcher dispatcher
|
||||||
clientsMutex *channelmutex.Mutex
|
clientsMutex *channelmutex.Mutex
|
||||||
|
|
||||||
// set this to true to support negentropy
|
// set this to true to support negentropy
|
||||||
|
|||||||
+35
-77
@@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
"fiatjaf.com/nostr"
|
"fiatjaf.com/nostr"
|
||||||
"fiatjaf.com/nostr/eventstore/slicestore"
|
"fiatjaf.com/nostr/eventstore/slicestore"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBasicRelayFunctionality(t *testing.T) {
|
func TestBasicRelayFunctionality(t *testing.T) {
|
||||||
@@ -46,15 +47,11 @@ func TestBasicRelayFunctionality(t *testing.T) {
|
|||||||
// connect two test clients
|
// connect two test clients
|
||||||
url := "ws" + server.URL[4:]
|
url := "ws" + server.URL[4:]
|
||||||
client1, err := nostr.RelayConnect(t.Context(), url, nostr.RelayOptions{})
|
client1, err := nostr.RelayConnect(t.Context(), url, nostr.RelayOptions{})
|
||||||
if err != nil {
|
require.NoError(t, err, "failed to connect client1")
|
||||||
t.Fatalf("failed to connect client1: %v", err)
|
|
||||||
}
|
|
||||||
defer client1.Close()
|
defer client1.Close()
|
||||||
|
|
||||||
client2, err := nostr.RelayConnect(t.Context(), url, nostr.RelayOptions{})
|
client2, err := nostr.RelayConnect(t.Context(), url, nostr.RelayOptions{})
|
||||||
if err != nil {
|
require.NoError(t, err, "failed to connect client2")
|
||||||
t.Fatalf("failed to connect client2: %v", err)
|
|
||||||
}
|
|
||||||
defer client2.Close()
|
defer client2.Close()
|
||||||
|
|
||||||
// test 1: store and query events
|
// test 1: store and query events
|
||||||
@@ -64,18 +61,14 @@ func TestBasicRelayFunctionality(t *testing.T) {
|
|||||||
|
|
||||||
evt1 := createEvent(sk1, 1, "hello world", nil)
|
evt1 := createEvent(sk1, 1, "hello world", nil)
|
||||||
err := client1.Publish(ctx, evt1)
|
err := client1.Publish(ctx, evt1)
|
||||||
if err != nil {
|
require.NoError(t, err, "failed to publish event")
|
||||||
t.Fatalf("failed to publish event: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Query the event back
|
// Query the event back
|
||||||
sub, err := client2.Subscribe(ctx, nostr.Filter{
|
sub, err := client2.Subscribe(ctx, nostr.Filter{
|
||||||
Authors: []nostr.PubKey{pk1},
|
Authors: []nostr.PubKey{pk1},
|
||||||
Kinds: []nostr.Kind{1},
|
Kinds: []nostr.Kind{1},
|
||||||
}, nostr.SubscriptionOptions{})
|
}, nostr.SubscriptionOptions{})
|
||||||
if err != nil {
|
require.NoError(t, err, "failed to subscribe")
|
||||||
t.Fatalf("failed to subscribe: %v", err)
|
|
||||||
}
|
|
||||||
defer sub.Unsub()
|
defer sub.Unsub()
|
||||||
|
|
||||||
// Wait for event
|
// Wait for event
|
||||||
@@ -85,7 +78,7 @@ func TestBasicRelayFunctionality(t *testing.T) {
|
|||||||
t.Errorf("got wrong event: %v", env.ID)
|
t.Errorf("got wrong event: %v", env.ID)
|
||||||
}
|
}
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
t.Fatal("timeout waiting for event")
|
require.FailNow(t, "timeout waiting for event")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -99,17 +92,13 @@ func TestBasicRelayFunctionality(t *testing.T) {
|
|||||||
Authors: []nostr.PubKey{pk2},
|
Authors: []nostr.PubKey{pk2},
|
||||||
Kinds: []nostr.Kind{1},
|
Kinds: []nostr.Kind{1},
|
||||||
}, nostr.SubscriptionOptions{})
|
}, nostr.SubscriptionOptions{})
|
||||||
if err != nil {
|
require.NoError(t, err, "failed to subscribe")
|
||||||
t.Fatalf("failed to subscribe: %v", err)
|
|
||||||
}
|
|
||||||
defer sub.Unsub()
|
defer sub.Unsub()
|
||||||
|
|
||||||
// Publish event from client2
|
// Publish event from client2
|
||||||
evt2 := createEvent(sk2, 1, "testing live events", nil)
|
evt2 := createEvent(sk2, 1, "testing live events", nil)
|
||||||
err = client2.Publish(ctx, evt2)
|
err = client2.Publish(ctx, evt2)
|
||||||
if err != nil {
|
require.NoError(t, err, "failed to publish event")
|
||||||
t.Fatalf("failed to publish event: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for event on subscription
|
// Wait for event on subscription
|
||||||
select {
|
select {
|
||||||
@@ -118,7 +107,7 @@ func TestBasicRelayFunctionality(t *testing.T) {
|
|||||||
t.Errorf("got wrong event: %v", env.ID)
|
t.Errorf("got wrong event: %v", env.ID)
|
||||||
}
|
}
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
t.Fatal("timeout waiting for live event")
|
require.FailNow(t, "timeout waiting for live event")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -130,24 +119,18 @@ func TestBasicRelayFunctionality(t *testing.T) {
|
|||||||
// Create an event to be deleted
|
// Create an event to be deleted
|
||||||
evt3 := createEvent(sk1, 1, "delete me", nil)
|
evt3 := createEvent(sk1, 1, "delete me", nil)
|
||||||
err = client1.Publish(ctx, evt3)
|
err = client1.Publish(ctx, evt3)
|
||||||
if err != nil {
|
require.NoError(t, err, "failed to publish event")
|
||||||
t.Fatalf("failed to publish event: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create deletion event
|
// Create deletion event
|
||||||
delEvent := createEvent(sk1, 5, "deleting", nostr.Tags{{"e", evt3.ID.Hex()}})
|
delEvent := createEvent(sk1, 5, "deleting", nostr.Tags{{"e", evt3.ID.Hex()}})
|
||||||
err = client1.Publish(ctx, delEvent)
|
err = client1.Publish(ctx, delEvent)
|
||||||
if err != nil {
|
require.NoError(t, err, "failed to publish deletion event")
|
||||||
t.Fatalf("failed to publish deletion event: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to query the deleted event
|
// Try to query the deleted event
|
||||||
sub, err := client2.Subscribe(ctx, nostr.Filter{
|
sub, err := client2.Subscribe(ctx, nostr.Filter{
|
||||||
IDs: []nostr.ID{evt3.ID},
|
IDs: []nostr.ID{evt3.ID},
|
||||||
}, nostr.SubscriptionOptions{})
|
}, nostr.SubscriptionOptions{})
|
||||||
if err != nil {
|
require.NoError(t, err, "failed to subscribe")
|
||||||
t.Fatalf("failed to subscribe: %v", err)
|
|
||||||
}
|
|
||||||
defer sub.Unsub()
|
defer sub.Unsub()
|
||||||
|
|
||||||
// Should get EOSE without receiving the deleted event
|
// Should get EOSE without receiving the deleted event
|
||||||
@@ -162,7 +145,7 @@ func TestBasicRelayFunctionality(t *testing.T) {
|
|||||||
}
|
}
|
||||||
goto checkDeleteStored
|
goto checkDeleteStored
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
t.Fatal("timeout waiting for EOSE")
|
require.FailNow(t, "timeout waiting for EOSE")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,9 +154,7 @@ func TestBasicRelayFunctionality(t *testing.T) {
|
|||||||
subDelete, err := client2.Subscribe(ctx, nostr.Filter{
|
subDelete, err := client2.Subscribe(ctx, nostr.Filter{
|
||||||
IDs: []nostr.ID{delEvent.ID},
|
IDs: []nostr.ID{delEvent.ID},
|
||||||
}, nostr.SubscriptionOptions{})
|
}, nostr.SubscriptionOptions{})
|
||||||
if err != nil {
|
require.NoError(t, err, "failed to subscribe to delete event")
|
||||||
t.Fatalf("failed to subscribe to delete event: %v", err)
|
|
||||||
}
|
|
||||||
defer subDelete.Unsub()
|
defer subDelete.Unsub()
|
||||||
|
|
||||||
gotDeleteEvent := false
|
gotDeleteEvent := false
|
||||||
@@ -189,7 +170,7 @@ func TestBasicRelayFunctionality(t *testing.T) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
t.Fatal("timeout waiting for EOSE on delete event")
|
require.FailNow(t, "timeout waiting for EOSE on delete event")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -204,36 +185,28 @@ func TestBasicRelayFunctionality(t *testing.T) {
|
|||||||
evt1.CreatedAt = 1000 // Set specific timestamp for testing
|
evt1.CreatedAt = 1000 // Set specific timestamp for testing
|
||||||
evt1.Sign(sk1)
|
evt1.Sign(sk1)
|
||||||
err = client1.Publish(ctx, evt1)
|
err = client1.Publish(ctx, evt1)
|
||||||
if err != nil {
|
require.NoError(t, err, "failed to publish initial event")
|
||||||
t.Fatalf("failed to publish initial event: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// create newer event that should replace the first
|
// create newer event that should replace the first
|
||||||
evt2 := createEvent(sk1, 0, `{"name":"newer"}`, nil)
|
evt2 := createEvent(sk1, 0, `{"name":"newer"}`, nil)
|
||||||
evt2.CreatedAt = 2004 // Newer timestamp
|
evt2.CreatedAt = 2004 // Newer timestamp
|
||||||
evt2.Sign(sk1)
|
evt2.Sign(sk1)
|
||||||
err = client1.Publish(ctx, evt2)
|
err = client1.Publish(ctx, evt2)
|
||||||
if err != nil {
|
require.NoError(t, err, "failed to publish newer event")
|
||||||
t.Fatalf("failed to publish newer event: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// create older event that should not replace the current one
|
// create older event that should not replace the current one
|
||||||
evt3 := createEvent(sk1, 0, `{"name":"older"}`, nil)
|
evt3 := createEvent(sk1, 0, `{"name":"older"}`, nil)
|
||||||
evt3.CreatedAt = 1500 // Older than evt2
|
evt3.CreatedAt = 1500 // Older than evt2
|
||||||
evt3.Sign(sk1)
|
evt3.Sign(sk1)
|
||||||
err = client1.Publish(ctx, evt3)
|
err = client1.Publish(ctx, evt3)
|
||||||
if err != nil {
|
require.NoError(t, err, "failed to publish older event")
|
||||||
t.Fatalf("failed to publish older event: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// query to verify only the newest event exists
|
// query to verify only the newest event exists
|
||||||
sub, err := client2.Subscribe(ctx, nostr.Filter{
|
sub, err := client2.Subscribe(ctx, nostr.Filter{
|
||||||
Authors: []nostr.PubKey{pk1},
|
Authors: []nostr.PubKey{pk1},
|
||||||
Kinds: []nostr.Kind{0},
|
Kinds: []nostr.Kind{0},
|
||||||
}, nostr.SubscriptionOptions{})
|
}, nostr.SubscriptionOptions{})
|
||||||
if err != nil {
|
require.NoError(t, err, "failed to subscribe")
|
||||||
t.Fatalf("failed to subscribe: %v", err)
|
|
||||||
}
|
|
||||||
defer sub.Unsub()
|
defer sub.Unsub()
|
||||||
|
|
||||||
// should only get one event back (the newest one)
|
// should only get one event back (the newest one)
|
||||||
@@ -251,7 +224,7 @@ func TestBasicRelayFunctionality(t *testing.T) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
t.Fatal("timeout waiting for events")
|
require.FailNow(t, "timeout waiting for events")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -281,26 +254,20 @@ func TestBasicRelayFunctionality(t *testing.T) {
|
|||||||
// connect test client
|
// connect test client
|
||||||
url := "ws" + server.URL[4:]
|
url := "ws" + server.URL[4:]
|
||||||
client, err := nostr.RelayConnect(t.Context(), url, nostr.RelayOptions{})
|
client, err := nostr.RelayConnect(t.Context(), url, nostr.RelayOptions{})
|
||||||
if err != nil {
|
require.NoError(t, err, "failed to connect client")
|
||||||
t.Fatalf("failed to connect client: %v", err)
|
|
||||||
}
|
|
||||||
defer client.Close()
|
defer client.Close()
|
||||||
|
|
||||||
// create event that expires in 2 seconds
|
// create event that expires in 2 seconds
|
||||||
expiration := strconv.FormatInt(int64(nostr.Now()+2), 10)
|
expiration := strconv.FormatInt(int64(nostr.Now()+2), 10)
|
||||||
evt := createEvent(sk1, 1, "i will expire soon", nostr.Tags{{"expiration", expiration}})
|
evt := createEvent(sk1, 1, "i will expire soon", nostr.Tags{{"expiration", expiration}})
|
||||||
err = client.Publish(ctx, evt)
|
err = client.Publish(ctx, evt)
|
||||||
if err != nil {
|
require.NoError(t, err, "failed to publish event")
|
||||||
t.Fatalf("failed to publish event: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// verify event exists initially
|
// verify event exists initially
|
||||||
sub, err := client.Subscribe(ctx, nostr.Filter{
|
sub, err := client.Subscribe(ctx, nostr.Filter{
|
||||||
IDs: []nostr.ID{evt.ID},
|
IDs: []nostr.ID{evt.ID},
|
||||||
}, nostr.SubscriptionOptions{})
|
}, nostr.SubscriptionOptions{})
|
||||||
if err != nil {
|
require.NoError(t, err, "failed to subscribe")
|
||||||
t.Fatalf("failed to subscribe: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// should get the event
|
// should get the event
|
||||||
select {
|
select {
|
||||||
@@ -309,7 +276,7 @@ func TestBasicRelayFunctionality(t *testing.T) {
|
|||||||
t.Error("got wrong event")
|
t.Error("got wrong event")
|
||||||
}
|
}
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
t.Fatal("timeout waiting for event")
|
require.FailNow(t, "timeout waiting for event")
|
||||||
}
|
}
|
||||||
sub.Unsub()
|
sub.Unsub()
|
||||||
|
|
||||||
@@ -320,9 +287,7 @@ func TestBasicRelayFunctionality(t *testing.T) {
|
|||||||
sub, err = client.Subscribe(ctx, nostr.Filter{
|
sub, err = client.Subscribe(ctx, nostr.Filter{
|
||||||
IDs: []nostr.ID{evt.ID},
|
IDs: []nostr.ID{evt.ID},
|
||||||
}, nostr.SubscriptionOptions{})
|
}, nostr.SubscriptionOptions{})
|
||||||
if err != nil {
|
require.NoError(t, err, "failed to subscribe")
|
||||||
t.Fatalf("failed to subscribe: %v", err)
|
|
||||||
}
|
|
||||||
defer sub.Unsub()
|
defer sub.Unsub()
|
||||||
|
|
||||||
// should get EOSE without receiving the expired event
|
// should get EOSE without receiving the expired event
|
||||||
@@ -337,7 +302,7 @@ func TestBasicRelayFunctionality(t *testing.T) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
t.Fatal("timeout waiting for EOSE")
|
require.FailNow(t, "timeout waiting for EOSE")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -350,33 +315,26 @@ func TestBasicRelayFunctionality(t *testing.T) {
|
|||||||
// create an event from client1
|
// create an event from client1
|
||||||
evt4 := createEvent(sk1, 1, "try to delete me", nil)
|
evt4 := createEvent(sk1, 1, "try to delete me", nil)
|
||||||
err = client1.Publish(ctx, evt4)
|
err = client1.Publish(ctx, evt4)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("failed to publish event: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to delete it with client2
|
// try to delete it with client2
|
||||||
delEvent := createEvent(sk2, 5, "trying to delete", nostr.Tags{{"e", evt4.ID.Hex()}})
|
delEvent := createEvent(sk2, 5, "trying to delete", nostr.Tags{{"e", evt4.ID.Hex()}})
|
||||||
err = client2.Publish(ctx, delEvent)
|
err = client2.Publish(ctx, delEvent)
|
||||||
if err == nil {
|
require.Error(t, err)
|
||||||
t.Fatalf("should have failed to publish deletion event: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify event still exists
|
// verify event still exists
|
||||||
sub, err := client1.Subscribe(ctx, nostr.Filter{
|
sub, err := client1.Subscribe(ctx, nostr.Filter{
|
||||||
IDs: []nostr.ID{evt4.ID},
|
IDs: []nostr.ID{evt4.ID},
|
||||||
}, nostr.SubscriptionOptions{})
|
}, nostr.SubscriptionOptions{})
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("failed to subscribe: %v", err)
|
|
||||||
}
|
|
||||||
defer sub.Unsub()
|
defer sub.Unsub()
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case env := <-sub.Events:
|
case env, more := <-sub.Events:
|
||||||
if env.ID != evt4.ID {
|
require.True(t, more, "should get an event, got nothing")
|
||||||
t.Error("got wrong event")
|
require.Equal(t, env.ID, evt4.ID, "got wrong event")
|
||||||
}
|
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
t.Fatal("event should still exist")
|
require.FailNow(t, "event should still exist")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,77 +0,0 @@
|
|||||||
package khatru
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fiatjaf.com/nostr"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Router struct{ *Relay }
|
|
||||||
|
|
||||||
type Route struct {
|
|
||||||
eventMatcher func(*nostr.Event) bool
|
|
||||||
filterMatcher func(nostr.Filter) bool
|
|
||||||
relay *Relay
|
|
||||||
}
|
|
||||||
|
|
||||||
type routeBuilder struct {
|
|
||||||
router *Router
|
|
||||||
eventMatcher func(*nostr.Event) bool
|
|
||||||
filterMatcher func(nostr.Filter) bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRouter() *Router {
|
|
||||||
rr := &Router{Relay: NewRelay()}
|
|
||||||
rr.routes = make([]Route, 0, 3)
|
|
||||||
rr.getSubRelayFromFilter = func(f nostr.Filter) *Relay {
|
|
||||||
for _, route := range rr.routes {
|
|
||||||
if route.filterMatcher == nil || route.filterMatcher(f) {
|
|
||||||
return route.relay
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return rr.Relay
|
|
||||||
}
|
|
||||||
rr.getSubRelayFromEvent = func(e *nostr.Event) *Relay {
|
|
||||||
for _, route := range rr.routes {
|
|
||||||
if route.eventMatcher == nil || route.eventMatcher(e) {
|
|
||||||
return route.relay
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return rr.Relay
|
|
||||||
}
|
|
||||||
return rr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rr *Router) Route() routeBuilder {
|
|
||||||
return routeBuilder{
|
|
||||||
router: rr,
|
|
||||||
filterMatcher: func(f nostr.Filter) bool { return false },
|
|
||||||
eventMatcher: func(e *nostr.Event) bool { return false },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rb routeBuilder) Req(fn func(nostr.Filter) bool) routeBuilder {
|
|
||||||
rb.filterMatcher = fn
|
|
||||||
return rb
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rb routeBuilder) AnyReq() routeBuilder {
|
|
||||||
rb.filterMatcher = nil
|
|
||||||
return rb
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rb routeBuilder) Event(fn func(*nostr.Event) bool) routeBuilder {
|
|
||||||
rb.eventMatcher = fn
|
|
||||||
return rb
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rb routeBuilder) AnyEvent() routeBuilder {
|
|
||||||
rb.eventMatcher = nil
|
|
||||||
return rb
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rb routeBuilder) Relay(relay *Relay) {
|
|
||||||
rb.router.routes = append(rb.router.routes, Route{
|
|
||||||
filterMatcher: rb.filterMatcher,
|
|
||||||
eventMatcher: rb.eventMatcher,
|
|
||||||
relay: relay,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user