206 lines
6.1 KiB
Go
206 lines
6.1 KiB
Go
package websocket
|
|
|
|
import (
|
|
"api-service/internal/config"
|
|
ws "api-service/internal/services/websocket"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/gorilla/websocket"
|
|
)
|
|
|
|
// WebSocketHandler menangani koneksi WebSocket
|
|
type WebSocketHandler struct {
|
|
hub *ws.Hub
|
|
broadcaster *ws.Broadcaster
|
|
config config.WebSocketConfig
|
|
upgrader websocket.Upgrader
|
|
monitoringManager *ws.MonitoringManager
|
|
}
|
|
|
|
// NewWebSocketHandler membuat handler WebSocket baru
|
|
func NewWebSocketHandler(hub *ws.Hub, broadcaster *ws.Broadcaster, config config.WebSocketConfig) *WebSocketHandler {
|
|
return &WebSocketHandler{
|
|
hub: hub,
|
|
broadcaster: broadcaster,
|
|
config: config,
|
|
monitoringManager: ws.NewMonitoringManager(hub),
|
|
upgrader: websocket.Upgrader{
|
|
ReadBufferSize: config.ReadBufferSize,
|
|
WriteBufferSize: config.WriteBufferSize,
|
|
CheckOrigin: func(r *http.Request) bool {
|
|
// Dalam production, implementasikan validasi origin yang proper
|
|
return true
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// HandleWebSocket menangani upgrade koneksi ke WebSocket
|
|
func (h *WebSocketHandler) HandleWebSocket(c *gin.Context) {
|
|
// Ambil parameter dari query atau header
|
|
clientID := c.Query("client_id")
|
|
if clientID == "" {
|
|
clientID = fmt.Sprintf("client_%d", time.Now().UnixNano())
|
|
}
|
|
|
|
staticID := c.Query("static_id")
|
|
userID := c.Query("user_id")
|
|
room := c.Query("room")
|
|
ipAddress := c.ClientIP()
|
|
|
|
// Upgrade koneksi HTTP ke WebSocket
|
|
conn, err := h.upgrader.Upgrade(c.Writer, c.Request, nil)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
"error": "Failed to upgrade connection",
|
|
"details": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
// Buat klien WebSocket baru
|
|
client := ws.NewClient(clientID, staticID, userID, room, ipAddress, conn, h.hub, h.config)
|
|
|
|
// Daftarkan klien ke hub
|
|
h.hub.RegisterChannel() <- client
|
|
|
|
// Jalankan goroutine untuk membaca dan menulis
|
|
go client.ReadPump()
|
|
go client.WritePump()
|
|
}
|
|
|
|
// GetWebSocketStats mengembalikan statistik WebSocket
|
|
func (h *WebSocketHandler) GetWebSocketStats(c *gin.Context) {
|
|
stats := h.hub.GetStats()
|
|
c.JSON(http.StatusOK, stats)
|
|
}
|
|
|
|
// TestWebSocketConnection endpoint untuk test koneksi WebSocket
|
|
func (h *WebSocketHandler) TestWebSocketConnection(c *gin.Context) {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"message": "WebSocket service is running",
|
|
"status": "active",
|
|
"endpoint": "/api/v1/ws",
|
|
"parameters": gin.H{
|
|
"client_id": "optional, auto-generated if not provided",
|
|
"static_id": "optional, for persistent client identification",
|
|
"user_id": "optional, for user identification",
|
|
"room": "optional, for room-based messaging",
|
|
},
|
|
"example": "ws://meninjar.dev.rssa.id:8070/api/v1/ws?client_id=test_client&room=test_room",
|
|
"timestamp": time.Now().Unix(),
|
|
})
|
|
}
|
|
|
|
// GetDetailedStats mengembalikan statistik detail WebSocket
|
|
func (h *WebSocketHandler) GetDetailedStats(c *gin.Context) {
|
|
detailedStats := h.monitoringManager.GetDetailedStats()
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"admin_stats": detailedStats,
|
|
"timestamp": time.Now().Unix(),
|
|
})
|
|
}
|
|
|
|
// DisconnectClient memutuskan koneksi klien tertentu
|
|
func (h *WebSocketHandler) DisconnectClient(c *gin.Context) {
|
|
clientID := c.Param("clientId")
|
|
success := h.monitoringManager.DisconnectClient(clientID)
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"status": "force disconnect attempted",
|
|
"client_id": clientID,
|
|
"success": success,
|
|
"timestamp": time.Now().Unix(),
|
|
})
|
|
}
|
|
|
|
// CleanupInactiveClients membersihkan klien tidak aktif
|
|
func (h *WebSocketHandler) CleanupInactiveClients(c *gin.Context) {
|
|
var req struct {
|
|
InactiveMinutes int `json:"inactive_minutes"`
|
|
Force bool `json:"force"`
|
|
}
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
req.InactiveMinutes = 10
|
|
req.Force = false
|
|
}
|
|
|
|
cleanedCount := h.monitoringManager.CleanupInactiveClients(time.Duration(req.InactiveMinutes) * time.Minute)
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"status": "admin cleanup completed",
|
|
"cleaned_clients": cleanedCount,
|
|
"inactive_minutes": req.InactiveMinutes,
|
|
"force": req.Force,
|
|
"timestamp": time.Now().Unix(),
|
|
})
|
|
}
|
|
|
|
// SendMessageToClient forwards a message to a specific connected client via an HTTP POST request.
|
|
// This version normalizes the data and uses the custom broadcaster method for clean delivery.
|
|
func (h *WebSocketHandler) SendMessageToClient(c *gin.Context) {
|
|
// Define a flexible struct to parse the incoming HTTP request body
|
|
var req struct {
|
|
Data interface{} `json:"data"`
|
|
ToClient string `json:"to_client"`
|
|
Type string `json:"type"`
|
|
}
|
|
|
|
// Parse the JSON from the HTTP request body
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Normalize data into map[string]interface{} for consistency
|
|
var dataMap map[string]interface{}
|
|
switch v := req.Data.(type) {
|
|
case map[string]interface{}:
|
|
dataMap = v
|
|
case string:
|
|
// If data is a JSON string, try to unmarshal it
|
|
if err := json.Unmarshal([]byte(v), &dataMap); err != nil {
|
|
// If unmarshaling fails, treat it as a raw string
|
|
dataMap = map[string]interface{}{"raw": v}
|
|
}
|
|
default:
|
|
// For any other type, wrap it in a map
|
|
dataMap = map[string]interface{}{"data": v}
|
|
}
|
|
|
|
// Get the target client ID, checking the body first, then the query parameter
|
|
to := req.ToClient
|
|
if to == "" {
|
|
to = c.Query("to")
|
|
}
|
|
if to == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "target client id required (field 'to' or query param 'to')"})
|
|
return
|
|
}
|
|
|
|
msgType := req.Type
|
|
if msgType == "" {
|
|
msgType = "direct_message"
|
|
}
|
|
|
|
// Use the existing broadcaster instance from the Hub.
|
|
// DO NOT create a new broadcaster on every request.
|
|
h.broadcaster.CustomBroadcastClientToClient(
|
|
"server", // fromClientID, indicating the message is from the server itself
|
|
to, // toClientID
|
|
msgType, // messageType, as specified in the request
|
|
dataMap, // data payload, already normalized
|
|
)
|
|
|
|
// Respond with a detailed confirmation
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"status": "sent",
|
|
"to_client": to,
|
|
"type": msgType,
|
|
"data": dataMap,
|
|
"timestamp": time.Now().Unix(),
|
|
})
|
|
}
|