diff options
| author | bndw <ben@bdw.to> | 2026-02-13 18:17:37 -0800 |
|---|---|---|
| committer | bndw <ben@bdw.to> | 2026-02-13 18:17:37 -0800 |
| commit | 3481c3273f8764bd0a0ab51183dc57f592fb616c (patch) | |
| tree | 8bef805f5f18420198a04bf87578bbc80b76ea40 /cmd/relay | |
| parent | ecd4a2240dd443fd6949e6e1120a7ec971a024ca (diff) | |
feat: add WebSocket server with full NIP-01 support
WebSocket handler:
- NIP-01 protocol (EVENT, REQ, CLOSE, OK, EOSE, NOTICE)
- JSON envelope parsing
- Shares subscription manager with gRPC (unified event fan-out)
- Standard Nostr client compatibility
Relay now serves dual protocols:
- gRPC on :50051 (binary, high performance)
- WebSocket on :8080 (JSON, Nostr standard)
Both protocols share:
- Same storage layer
- Same subscription manager
- Same validation logic
Compatible with all Nostr clients!
Diffstat (limited to 'cmd/relay')
| -rw-r--r-- | cmd/relay/main.go | 36 |
1 files changed, 30 insertions, 6 deletions
diff --git a/cmd/relay/main.go b/cmd/relay/main.go index 3db466d..53296b9 100644 --- a/cmd/relay/main.go +++ b/cmd/relay/main.go | |||
| @@ -4,20 +4,26 @@ import ( | |||
| 4 | "flag" | 4 | "flag" |
| 5 | "log" | 5 | "log" |
| 6 | "net" | 6 | "net" |
| 7 | "net/http" | ||
| 7 | "os" | 8 | "os" |
| 8 | "os/signal" | 9 | "os/signal" |
| 9 | "syscall" | 10 | "syscall" |
| 10 | 11 | ||
| 12 | "context" | ||
| 13 | |||
| 11 | "google.golang.org/grpc" | 14 | "google.golang.org/grpc" |
| 12 | 15 | ||
| 13 | pb "northwest.io/nostr-grpc/api/nostr/v1" | 16 | pb "northwest.io/nostr-grpc/api/nostr/v1" |
| 14 | grpchandler "northwest.io/nostr-grpc/internal/handler/grpc" | 17 | grpchandler "northwest.io/nostr-grpc/internal/handler/grpc" |
| 18 | wshandler "northwest.io/nostr-grpc/internal/handler/websocket" | ||
| 15 | "northwest.io/nostr-grpc/internal/storage" | 19 | "northwest.io/nostr-grpc/internal/storage" |
| 20 | "northwest.io/nostr-grpc/internal/subscription" | ||
| 16 | ) | 21 | ) |
| 17 | 22 | ||
| 18 | func main() { | 23 | func main() { |
| 19 | var ( | 24 | var ( |
| 20 | grpcAddr = flag.String("grpc-addr", ":50051", "gRPC server address") | 25 | grpcAddr = flag.String("grpc-addr", ":50051", "gRPC server address") |
| 26 | wsAddr = flag.String("ws-addr", ":8080", "WebSocket server address") | ||
| 21 | dbPath = flag.String("db", "relay.db", "SQLite database path") | 27 | dbPath = flag.String("db", "relay.db", "SQLite database path") |
| 22 | ) | 28 | ) |
| 23 | flag.Parse() | 29 | flag.Parse() |
| @@ -28,17 +34,28 @@ func main() { | |||
| 28 | } | 34 | } |
| 29 | defer store.Close() | 35 | defer store.Close() |
| 30 | 36 | ||
| 31 | handler := grpchandler.NewServer(store) | 37 | subManager := subscription.NewManager() |
| 38 | |||
| 39 | grpcHandler := grpchandler.NewServer(store) | ||
| 40 | grpcHandler.SetSubscriptionManager(subManager) | ||
| 32 | 41 | ||
| 33 | lis, err := net.Listen("tcp", *grpcAddr) | 42 | wsHandler := wshandler.NewHandler(store, subManager) |
| 43 | |||
| 44 | grpcLis, err := net.Listen("tcp", *grpcAddr) | ||
| 34 | if err != nil { | 45 | if err != nil { |
| 35 | log.Fatalf("failed to listen: %v", err) | 46 | log.Fatalf("failed to listen on gRPC port: %v", err) |
| 36 | } | 47 | } |
| 37 | 48 | ||
| 38 | grpcServer := grpc.NewServer() | 49 | grpcServer := grpc.NewServer() |
| 39 | pb.RegisterNostrRelayServer(grpcServer, handler) | 50 | pb.RegisterNostrRelayServer(grpcServer, grpcHandler) |
| 51 | |||
| 52 | httpServer := &http.Server{ | ||
| 53 | Addr: *wsAddr, | ||
| 54 | Handler: wsHandler, | ||
| 55 | } | ||
| 40 | 56 | ||
| 41 | log.Printf("gRPC server listening on %s", *grpcAddr) | 57 | log.Printf("gRPC server listening on %s", *grpcAddr) |
| 58 | log.Printf("WebSocket server listening on %s", *wsAddr) | ||
| 42 | log.Printf("Database: %s", *dbPath) | 59 | log.Printf("Database: %s", *dbPath) |
| 43 | 60 | ||
| 44 | sigChan := make(chan os.Signal, 1) | 61 | sigChan := make(chan os.Signal, 1) |
| @@ -48,9 +65,16 @@ func main() { | |||
| 48 | <-sigChan | 65 | <-sigChan |
| 49 | log.Println("Shutting down...") | 66 | log.Println("Shutting down...") |
| 50 | grpcServer.GracefulStop() | 67 | grpcServer.GracefulStop() |
| 68 | httpServer.Shutdown(context.Background()) | ||
| 69 | }() | ||
| 70 | |||
| 71 | go func() { | ||
| 72 | if err := grpcServer.Serve(grpcLis); err != nil { | ||
| 73 | log.Fatalf("gRPC server failed: %v", err) | ||
| 74 | } | ||
| 51 | }() | 75 | }() |
| 52 | 76 | ||
| 53 | if err := grpcServer.Serve(lis); err != nil { | 77 | if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { |
| 54 | log.Fatalf("failed to serve: %v", err) | 78 | log.Fatalf("WebSocket server failed: %v", err) |
| 55 | } | 79 | } |
| 56 | } | 80 | } |
