diff --git a/main.go b/main.go index d112458..e451bb5 100644 --- a/main.go +++ b/main.go @@ -64,7 +64,7 @@ func main() { relay.Info.Software = "https://github.com/layer-systems/relay" relay.Info.Version = "0.1.1" - relay.Info.SupportedNIPs = []any{1, 11, 17, 40, 42, 70, 86} + relay.Info.SupportedNIPs = []any{1, 11, 17, 40, 42, 46, 70, 86} // Open shared database connection with aggressive pool limits managementDB, err := sql.Open("postgres", getEnv("DATABASE_URL", "postgresql://postgres:postgres@db:5432/khatru-relay?sslmode=disable")) diff --git a/nip46_integration_test.go b/nip46_integration_test.go new file mode 100644 index 0000000..e3c8ab2 --- /dev/null +++ b/nip46_integration_test.go @@ -0,0 +1,111 @@ +package main + +import ( + "context" + "net/http/httptest" + "os" + "testing" + "time" + + "github.com/fiatjaf/eventstore/slicestore" + "github.com/fiatjaf/khatru" + "github.com/fiatjaf/khatru/policies" + "github.com/nbd-wtf/go-nostr" +) + +func TestNIP46Transport(t *testing.T) { + url := os.Getenv("NIP46_RELAY_URL") + var store *slicestore.SliceStore + if url == "" { + relay := khatru.NewRelay() + store = &slicestore.SliceStore{} + store.Init() + relay.StoreEvent = append(relay.StoreEvent, store.SaveEvent) + relay.QueryEvents = append(relay.QueryEvents, store.QueryEvents) + relay.RejectEvent = append(relay.RejectEvent, policies.ValidateKind) + policies.ApplySaneDefaults(relay) + relay.RejectFilter = append(relay.RejectFilter, RejectNonAuthenticatedGiftWrapQueries) + + server := httptest.NewServer(relay) + defer server.Close() + url = "ws" + server.URL[4:] + } + + clientSecret := nostr.GeneratePrivateKey() + clientPublic, err := nostr.GetPublicKey(clientSecret) + if err != nil { + t.Fatal(err) + } + signerSecret := nostr.GeneratePrivateKey() + signerPublic, err := nostr.GetPublicKey(signerSecret) + if err != nil { + t.Fatal(err) + } + + event := nostr.Event{ + PubKey: clientPublic, + CreatedAt: nostr.Now(), + Kind: nostr.KindNostrConnect, + Tags: nostr.Tags{{"p", signerPublic}}, + Content: "opaque-nip44-payload", + } + if err := event.Sign(clientSecret); err != nil { + t.Fatal(err) + } + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + receiver, err := nostr.RelayConnect(ctx, url) + if err != nil { + t.Fatal(err) + } + defer receiver.Close() + sender, err := nostr.RelayConnect(ctx, url) + if err != nil { + t.Fatal(err) + } + defer sender.Close() + + sub, err := receiver.Subscribe(ctx, nostr.Filters{{ + Kinds: []int{nostr.KindNostrConnect}, + Tags: nostr.TagMap{"p": []string{signerPublic}}, + Since: &event.CreatedAt, + LimitZero: true, + }}) + if err != nil { + t.Fatal(err) + } + defer sub.Unsub() + select { + case <-sub.EndOfStoredEvents: + case reason := <-sub.ClosedReason: + t.Fatalf("subscription closed: %s", reason) + case <-ctx.Done(): + t.Fatal("timed out waiting for EOSE") + } + + if err := sender.Publish(ctx, event); err != nil { + t.Fatalf("publish NIP-46 event: %v", err) + } + + select { + case received := <-sub.Events: + if received.ID != event.ID { + t.Fatalf("received event %s, want %s", received.ID, event.ID) + } + case <-ctx.Done(): + t.Fatal("timed out waiting for NIP-46 event") + } + + if store != nil { + queryCtx, queryCancel := context.WithTimeout(context.Background(), time.Second) + defer queryCancel() + stored, err := store.QueryEvents(queryCtx, nostr.Filter{IDs: []string{event.ID}}) + if err != nil { + t.Fatal(err) + } + if got := <-stored; got != nil { + t.Fatalf("ephemeral NIP-46 event was stored: %s", got.ID) + } + } +}