Files
mc-router/server/webhook_notifier.go
T
2025-04-24 20:58:43 -05:00

168 lines
4.3 KiB
Go

package server
import (
"bytes"
"context"
"encoding/json"
"fmt"
"github.com/sirupsen/logrus"
"log"
"net"
"net/http"
"time"
)
// WebhookNotifier implements ConnectionNotifier by sending a POST request to a webhook URL.
// The payload is a JSON object defined by WebhookNotifierPayload.
type WebhookNotifier struct {
url string
requireUser bool
client *http.Client
}
const (
WebhookEventConnecting = "connect"
WebhookEventDisconnecting = "disconnect"
)
const (
WebhookStatusMissingBackend = "missing-backend"
WebhookStatusFailedBackendConnection = "failed-backend-connection"
WebhookStatusSuccess = "success"
)
type WebhookNotifierPayload struct {
Event string `json:"event"`
Timestamp time.Time `json:"timestamp"`
Status string `json:"status"`
Client *ClientInfo `json:"client"`
Server string `json:"server"`
PlayerInfo *PlayerInfo `json:"player,omitempty"`
BackendHostPort string `json:"backend,omitempty"`
Error string `json:"error,omitempty"`
}
func NewWebhookNotifier(url string, requireUser bool) *WebhookNotifier {
return &WebhookNotifier{
url: url,
requireUser: requireUser,
client: &http.Client{
Timeout: 30 * time.Second,
},
}
}
func (w *WebhookNotifier) NotifyMissingBackend(ctx context.Context, clientAddr net.Addr, server string, playerInfo *PlayerInfo) error {
if w.requireUser && playerInfo == nil {
return nil
}
payload := &WebhookNotifierPayload{
Event: WebhookEventConnecting,
Timestamp: time.Now(),
Status: WebhookStatusMissingBackend,
Client: ClientInfoFromAddr(clientAddr),
Server: server,
PlayerInfo: playerInfo,
Error: "No backend found",
}
return w.send(ctx, payload)
}
func (w *WebhookNotifier) NotifyFailedBackendConnection(ctx context.Context, clientAddr net.Addr, server string,
playerInfo *PlayerInfo, backendHostPort string, err error) error {
if w.requireUser && playerInfo == nil {
return nil
}
payload := &WebhookNotifierPayload{
Event: WebhookEventConnecting,
Timestamp: time.Now(),
Status: WebhookStatusFailedBackendConnection,
Client: ClientInfoFromAddr(clientAddr),
Server: server,
PlayerInfo: playerInfo,
BackendHostPort: backendHostPort,
Error: err.Error(),
}
return w.send(ctx, payload)
}
func (w *WebhookNotifier) NotifyConnected(ctx context.Context, clientAddr net.Addr, serverAddress string, playerInfo *PlayerInfo, backendHostPort string) error {
if w.requireUser && playerInfo == nil {
return nil
}
payload := &WebhookNotifierPayload{
Event: WebhookEventConnecting,
Timestamp: time.Now(),
Status: WebhookStatusSuccess,
Client: ClientInfoFromAddr(clientAddr),
Server: serverAddress,
PlayerInfo: playerInfo,
BackendHostPort: backendHostPort,
}
return w.send(ctx, payload)
}
func (w *WebhookNotifier) NotifyDisconnected(ctx context.Context, clientAddr net.Addr, serverAddress string, playerInfo *PlayerInfo, backendHostPort string) error {
if w.requireUser && playerInfo == nil {
return nil
}
payload := &WebhookNotifierPayload{
Event: WebhookEventDisconnecting,
Timestamp: time.Now(),
Status: WebhookStatusSuccess,
Client: ClientInfoFromAddr(clientAddr),
Server: serverAddress,
PlayerInfo: playerInfo,
BackendHostPort: backendHostPort,
}
return w.send(ctx, payload)
}
func (w *WebhookNotifier) send(ctx context.Context, payload *WebhookNotifierPayload) error {
jsonPayload, err := json.Marshal(payload)
if err != nil {
return fmt.Errorf("failed to marshal webhook payload: %v", err)
}
req, err := http.NewRequestWithContext(
ctx,
http.MethodPost,
w.url,
bytes.NewBuffer(jsonPayload),
)
if err != nil {
return fmt.Errorf("failed to create webhook request: %v", err)
}
req.Header.Set("Content-Type", "application/json")
go func() {
resp, err := w.client.Do(req)
if err != nil {
// Handle error
log.Printf("Failed to send webhook notification: %v", err)
return
}
_ = resp.Body.Close()
if resp.StatusCode >= 400 {
logrus.
WithField("status", resp.StatusCode).
Warn("webhook receiver responded with an error")
}
}()
return nil
}