* increases timeout * refactor: reuse constants for redis db * avoid closing connection on pong error * fix: prevent electron from pausing timer executions Our pings could be paused when our app is backgrounded. * refactor: remove unnecessary refs * fix: presence unreliable within same pod It was broadcasting presence changes to other pods, but not notifying clients connected to the same pod
113 lines
3.0 KiB
Go
113 lines
3.0 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log/slog"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
|
|
"github.com/flowy-live/llink/internal"
|
|
"github.com/flowy-live/llink/internal/auth"
|
|
"github.com/flowy-live/llink/internal/db"
|
|
"github.com/flowy-live/llink/internal/network"
|
|
"github.com/flowy-live/llink/internal/pusher"
|
|
"github.com/flowy-live/llink/internal/utils"
|
|
|
|
pbpusher "github.com/flowy-live/llink/genproto/llink/pusher"
|
|
"google.golang.org/grpc"
|
|
)
|
|
|
|
func main() {
|
|
port := utils.MustGetEnv("PORT")
|
|
grpcPort := utils.MustGetEnv("GRPC_PORT")
|
|
|
|
// Initialize database (for network membership checks)
|
|
db.Init()
|
|
defer db.Cleanup()
|
|
|
|
// Redis for auth session validation (same DB as orion)
|
|
authRedis := internal.ConnectAndTestRedis(db.RedisDBAuth)
|
|
|
|
// Redis for pusher state (presence hashes, pub/sub)
|
|
pusherRedis := internal.ConnectAndTestRedis(db.RedisDBPusher)
|
|
|
|
// Services
|
|
authSvc := auth.NewAuthService(authRedis, nil) // nil aeroSvc — pusher only calls GetSession
|
|
networkSvc := network.NewService(db.Pool())
|
|
|
|
// Pod identity (use hostname in k8s, which is the pod name)
|
|
podID, err := os.Hostname()
|
|
if err != nil {
|
|
podID = fmt.Sprintf("pod-%d", os.Getpid())
|
|
}
|
|
|
|
// Pusher core
|
|
bridge := pusher.NewRedisBridge(pusherRedis, podID)
|
|
// Context for graceful shutdown
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
authorizer := pusher.NewAuthorizer(networkSvc)
|
|
hub := pusher.NewHub(bridge, authorizer)
|
|
bridge.SetHub(hub)
|
|
server := pusher.NewServer(ctx, hub, bridge, authSvc)
|
|
|
|
// Start hub event loop
|
|
go hub.Run(ctx)
|
|
|
|
// Start Redis Pub/Sub listener
|
|
go bridge.Listen(ctx)
|
|
|
|
// Start pod heartbeat + stale pod cleanup
|
|
go bridge.Heartbeat(ctx)
|
|
|
|
// --- gRPC server (internal, for presence queries) ---
|
|
grpcListener, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%s", grpcPort))
|
|
if err != nil {
|
|
slog.Error("failed to listen for gRPC", "port", grpcPort, "error", err)
|
|
os.Exit(1)
|
|
}
|
|
grpcServer := grpc.NewServer()
|
|
pbpusher.RegisterPusherServiceServer(grpcServer, server)
|
|
go func() {
|
|
slog.Info("gRPC server listening", "port", grpcPort)
|
|
if err := grpcServer.Serve(grpcListener); err != nil {
|
|
slog.Error("gRPC server failed", "error", err)
|
|
}
|
|
}()
|
|
|
|
// --- HTTP server (WebSocket + health) ---
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
})
|
|
mux.HandleFunc("GET /ws", server.HandleWebSocket)
|
|
|
|
httpAddr := fmt.Sprintf("0.0.0.0:%s", port)
|
|
httpServer := &http.Server{Addr: httpAddr, Handler: mux}
|
|
|
|
go func() {
|
|
slog.Info("HTTP server listening", "addr", httpAddr)
|
|
if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
slog.Error("HTTP server failed", "error", err)
|
|
os.Exit(1)
|
|
}
|
|
}()
|
|
|
|
// --- Graceful shutdown ---
|
|
sigCh := make(chan os.Signal, 1)
|
|
signal.Notify(sigCh, syscall.SIGTERM, syscall.SIGINT)
|
|
<-sigCh
|
|
|
|
slog.Info("shutting down...")
|
|
cancel() // stops hub, bridge listener, heartbeat
|
|
|
|
grpcServer.GracefulStop()
|
|
httpServer.Shutdown(context.Background())
|
|
slog.Info("shutdown complete")
|
|
}
|