package main import ( "fmt" "log" "net/http" "os" "path/filepath" "time" "github.com/gin-gonic/gin" socketio "github.com/googollee/go-socket.io" "github.com/joho/godotenv" ) func main() { exePath, _ := os.Executable() exeDir := filepath.Dir(exePath) if err := godotenv.Load(filepath.Join(exeDir, ".env")); err != nil { // fallback dev path _ = godotenv.Load("../.env") } // gin stuff basePath := "/api/controller" if os.Getenv("NODE_ENV") != "production" { basePath = "/lst/api/controller" } port := os.Getenv("PORT") if port == "" { port = "8080" } server := socketio.NewServer(nil) r := Setup(basePath, server) // returns *gin.Engine server.OnConnect("/", func(s socketio.Conn) error { fmt.Println("✅ Client connected:", s.ID()) return nil }) // ROOM SUBSCRIBE server.OnEvent("/", "subscribe:logs", func(s socketio.Conn) { s.Join("logs") fmt.Println("📺", s.ID(), "joined logs") s.Emit("info", "Subscribed to logs") }) server.OnEvent("/", "unsubscribe:logs", func(s socketio.Conn) { s.Leave("logs") fmt.Println("👋", s.ID(), "left logs") s.Emit("info", "Unsubscribed from logs") }) server.OnEvent("/", "subscribe:errors", func(s socketio.Conn) { s.Join("errors") fmt.Println("📺", s.ID(), "joined errors") s.Emit("info", "Subscribed to errors") }) server.OnEvent("/", "unsubscribe:errors", func(s socketio.Conn) { s.Leave("errors") fmt.Println("👋", s.ID(), "left errors") s.Emit("info", "Unsubscribed from errors") }) server.OnDisconnect("/", func(s socketio.Conn, reason string) { fmt.Println("❌ Client disconnected:", s.ID(), reason) }) // build stuff // Subscribe to build room server.OnEvent("/", "subscribe:build", func(s socketio.Conn) { s.Join("build") fmt.Println("📺", s.ID(), "joined build room") s.Emit("info", "Subscribed to build log room") }) server.OnEvent("/", "unsubscribe:build", func(s socketio.Conn) { s.Leave("build") fmt.Println("👋", s.ID(), "left build room") s.Emit("info", "Unsubscribed from build log room") }) registerBuildChannel(server) registerUpdateChannel(server) // Broadcast logs to room go func() { for i := 0; ; i++ { time.Sleep(2 * time.Second) msg := fmt.Sprintf("Log line %d @ %s", i, time.Now().Format(time.RFC3339)) server.BroadcastToRoom("/", "logs", "logs", msg) } }() // Broadcast errors to room go func() { for i := 0; ; i++ { time.Sleep(5 * time.Second) msg := fmt.Sprintf("Error #%d @ %s", i, time.Now().Format(time.RFC3339)) server.BroadcastToRoom("/", "errors", "errors", msg) } }() go server.Serve() defer server.Close() // mount socket.io on /socket.io/* r.Any("/socket.io/*any", gin.WrapH(withCORS(server))) // mount a static dir (like http.FileServer) //r.Static("/", "./static") fmt.Println("🚀 Server running on :" + port) if err := r.Run(":" + port); err != nil { log.Fatal("Server failed:", err) } } // Reuse your proper CORS handler func withCORS(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { origin := r.Header.Get("Origin") if origin != "" { // 🔑 Echo the request Origin, not "*" w.Header().Set("Access-Control-Allow-Origin", origin) w.Header().Set("Vary", "Origin") } w.Header().Set("Access-Control-Allow-Methods", "GET,POST,OPTIONS") w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization") w.Header().Set("Access-Control-Allow-Credentials", "true") if r.Method == http.MethodOptions { w.WriteHeader(http.StatusNoContent) return } h.ServeHTTP(w, r) }) }