package relay import ( "context" "encoding/json" "log/slog" "time" "openflare-relay/internal/config" "openflare-relay/internal/frps" "openflare-relay/internal/heartbeat" "openflare-relay/internal/httpclient" "openflare-relay/internal/state" "openflare-relay/internal/wsclient" "openflare/service" ) type Runner struct { Config *config.Config StateStore *state.Store HeartbeatService *heartbeat.Service FrpsManager *frps.Manager WebSocketService *wsclient.Client HttpClient *httpclient.Client } func (r *Runner) Run(ctx context.Context) error { // Start heartbeat loop in background go r.HeartbeatService.Run(ctx) // WebSocket reconnection loop for { select { case <-ctx.Done(): r.FrpsManager.Stop() return ctx.Err() default: } conn, err := r.WebSocketService.Connect(ctx) if err != nil { slog.Error("relay ws connect failed, will retry", "error", err) r.sleepContext(ctx, 5*time.Second) continue } r.handleConnection(ctx, conn) _ = conn.Close() slog.Info("relay ws connection closed, reconnecting...") r.sleepContext(ctx, 2*time.Second) } } func (r *Runner) handleConnection(ctx context.Context, conn *wsclient.Connection) { for { select { case <-ctx.Done(): return default: } msg, err := conn.Receive() if err != nil { slog.Error("relay ws receive failed", "error", err) return } switch msg.Type { case "ping": _ = conn.SendPong() case "relay_config": payloadBytes, ok := msg.Payload.(json.RawMessage) if !ok { slog.Error("invalid relay_config payload type") continue } var cfg service.RelayConfig if err := json.Unmarshal(payloadBytes, &cfg); err != nil { slog.Error("failed to unmarshal relay_config", "error", err) continue } r.FrpsManager.UpdateConfig(&cfg) default: slog.Debug("ignored unknown ws message type", "type", msg.Type) } } } func (r *Runner) sleepContext(ctx context.Context, d time.Duration) { select { case <-ctx.Done(): case <-time.After(d): } }