From de4eff64d173e063998782f78786809026908adf Mon Sep 17 00:00:00 2001 From: fiatjaf Date: Tue, 20 Jan 2026 20:45:07 -0300 Subject: [PATCH] nip46: switch_relays on the client side. --- nip46/client.go | 32 +++++++++++++++++++++++++++----- nip46/nostrconnect.go | 24 +++++++++++------------- 2 files changed, 38 insertions(+), 18 deletions(-) diff --git a/nip46/client.go b/nip46/client.go index a762f86..9603150 100644 --- a/nip46/client.go +++ b/nip46/client.go @@ -16,11 +16,12 @@ import ( ) type BunkerClient struct { + Relays []string + serial atomic.Uint64 clientSecretKey [32]byte pool *nostr.Pool target nostr.PubKey - relays []string conversationKey [32]byte // nip44 listeners *xsync.MapOf[string, chan Response] idPrefix string @@ -116,23 +117,27 @@ func NewBunker( clientPublicKey := nostr.GetPublicKey(clientSecretKey) conversationKey, _ := nip44.GenerateConversationKey(targetPublicKey, clientSecretKey) + now := nostr.Now() bunker := &BunkerClient{ pool: pool, clientSecretKey: clientSecretKey, target: targetPublicKey, - relays: relays, + Relays: relays, conversationKey: conversationKey, listeners: xsync.NewMapOf[string, chan Response](), onAuth: onAuth, idPrefix: "nl-" + strconv.Itoa(rand.Intn(65536)), } + cancellableCtx, cancel := context.WithCancel(ctx) + _ = cancel + go func() { - events := pool.SubscribeMany(ctx, relays, nostr.Filter{ + events := pool.SubscribeMany(cancellableCtx, relays, nostr.Filter{ Tags: nostr.TagMap{"p": []string{clientPublicKey.Hex()}}, Kinds: []nostr.Kind{nostr.KindNostrConnect}, - Since: nostr.Now(), + Since: now, LimitZero: true, }, nostr.SubscriptionOptions{ Label: "bunker46client", @@ -167,6 +172,14 @@ func NewBunker( } }() + // attempt switch_relays once every 10 times + if now%10 == 0 { + if newRelays, _ := bunker.SwitchRelays(ctx); newRelays != nil { + cancel() + bunker = NewBunker(ctx, clientSecretKey, targetPublicKey, newRelays, pool, func(string) {}) + } + } + return bunker } @@ -178,6 +191,15 @@ func (bunker *BunkerClient) Ping(ctx context.Context) error { return nil } +func (bunker *BunkerClient) SwitchRelays(ctx context.Context) ([]string, error) { + var res []string + _, err := bunker.RPC(ctx, "switch_relays", res) + if err != nil { + return nil, err + } + return res, nil +} + func (bunker *BunkerClient) GetPublicKey(ctx context.Context) (nostr.PubKey, error) { if bunker.getPublicKeyResponse != nostr.ZeroPK { return bunker.getPublicKeyResponse, nil @@ -283,7 +305,7 @@ func (bunker *BunkerClient) RPC(ctx context.Context, method string, params []str relayConnectionWorked := make(chan struct{}) bunkerConnectionWorked := make(chan struct{}) - for _, url := range bunker.relays { + for _, url := range bunker.Relays { go func(url string) { relay, err := bunker.pool.EnsureRelay(url) if err == nil { diff --git a/nip46/nostrconnect.go b/nip46/nostrconnect.go index 2c1821c..73795f9 100644 --- a/nip46/nostrconnect.go +++ b/nip46/nostrconnect.go @@ -5,14 +5,11 @@ import ( "crypto/rand" "errors" "fmt" - mrand "math/rand" "net/url" - "strconv" "strings" "fiatjaf.com/nostr" "fiatjaf.com/nostr/nip44" - "github.com/puzpuzpuz/xsync/v3" ) var NoConnectionReceived = errors.New("relay connections ended without a bunker connection established") @@ -112,16 +109,17 @@ func NewBunkerFromNostrConnect( if req.Result != "" { if req.Result == secret { // secret validation passed - connection established - return &BunkerClient{ - pool: pool, - clientSecretKey: clientSecretKey, - target: targetPublicKey, - relays: relayURLs, - conversationKey: conversationKey, - listeners: xsync.NewMapOf[string, chan Response](), - onAuth: func(string) {}, - idPrefix: "nl-" + strconv.Itoa(mrand.Intn(65536)), - }, nil + cancellableCtx, cancel := context.WithCancel(ctx) + _ = cancel + bunker := NewBunker(cancellableCtx, clientSecretKey, targetPublicKey, relayURLs, pool, func(string) {}) + + // attempt switch_relays + if newRelays, _ := bunker.SwitchRelays(ctx); newRelays != nil { + cancel() + bunker = NewBunker(ctx, clientSecretKey, targetPublicKey, newRelays, pool, func(string) {}) + } + + return bunker, nil } } }