Improve SSE status and event auth handling
This commit is contained in:
@@ -9,6 +9,13 @@ import (
|
||||
func bearerAuthMiddleware(requiredToken string) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/api/events" {
|
||||
if r.URL.Query().Get("access_token") == requiredToken {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
auth := r.Header.Get("Authorization")
|
||||
if !strings.HasPrefix(auth, "Bearer ") {
|
||||
writeError(w, http.StatusUnauthorized, "Missing or invalid Authorization header")
|
||||
|
||||
@@ -2,6 +2,7 @@ package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/local-mcp/local-mcp-go/internal/events"
|
||||
)
|
||||
@@ -24,6 +25,8 @@ func handleSSE(broker *events.Broker) http.HandlerFunc {
|
||||
// Subscribe to event broker
|
||||
ch := broker.Subscribe()
|
||||
defer broker.Unsubscribe(ch)
|
||||
heartbeat := time.NewTicker(15 * time.Second)
|
||||
defer heartbeat.Stop()
|
||||
|
||||
// Send initial connection event
|
||||
w.Write([]byte("data: {\"type\":\"connected\"}\n\n"))
|
||||
@@ -38,6 +41,9 @@ func handleSSE(broker *events.Broker) http.HandlerFunc {
|
||||
}
|
||||
w.Write(msg)
|
||||
flusher.Flush()
|
||||
case <-heartbeat.C:
|
||||
w.Write([]byte(": keepalive\n\n"))
|
||||
flusher.Flush()
|
||||
case <-r.Context().Done():
|
||||
return // client disconnected
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package api
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/local-mcp/local-mcp-go/internal/models"
|
||||
)
|
||||
|
||||
func handleHealth() http.HandlerFunc {
|
||||
@@ -16,28 +18,58 @@ func handleHealth() http.HandlerFunc {
|
||||
|
||||
func handleStatus(stores Stores) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
counts, _ := stores.Instructions.Counts()
|
||||
latest, _ := stores.Agents.Latest()
|
||||
cfg, _ := stores.Settings.Get()
|
||||
counts, err := stores.Instructions.Counts()
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
resp := map[string]any{
|
||||
"uptime_seconds": int(time.Since(serverStartTime).Seconds()),
|
||||
"queue_pending": counts.PendingCount,
|
||||
"queue_consumed": counts.ConsumedCount,
|
||||
"agent_stale_after_seconds": cfg.AgentStaleAfterSeconds,
|
||||
cfg, err := stores.Settings.Get()
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
latest, err := stores.Agents.Latest()
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
agent := map[string]any{
|
||||
"connected": false,
|
||||
"last_seen_at": nil,
|
||||
"last_fetch_at": nil,
|
||||
"agent_id": nil,
|
||||
}
|
||||
|
||||
if latest != nil {
|
||||
isStale := time.Since(latest.LastSeenAt).Seconds() > float64(cfg.AgentStaleAfterSeconds)
|
||||
resp["agent"] = map[string]any{
|
||||
"agent_id": latest.AgentID,
|
||||
"last_fetch_at": latest.LastFetchAt.Format(time.RFC3339Nano),
|
||||
"last_result_type": latest.LastResultType,
|
||||
"is_stale": isStale,
|
||||
connected := time.Since(latest.LastSeenAt).Seconds() <= float64(cfg.AgentStaleAfterSeconds)
|
||||
agent = map[string]any{
|
||||
"connected": connected,
|
||||
"last_seen_at": latest.LastSeenAt.Format(time.RFC3339Nano),
|
||||
"last_fetch_at": latest.LastFetchAt.Format(time.RFC3339Nano),
|
||||
"agent_id": latest.AgentID,
|
||||
}
|
||||
}
|
||||
|
||||
resp := map[string]any{
|
||||
"server": map[string]any{
|
||||
"status": "up",
|
||||
"started_at": serverStartTime.Format(time.RFC3339Nano),
|
||||
},
|
||||
"agent": agent,
|
||||
"queue": map[string]any{
|
||||
"pending_count": counts.PendingCount,
|
||||
"consumed_count": counts.ConsumedCount,
|
||||
},
|
||||
"settings": models.Settings{
|
||||
DefaultWaitSeconds: cfg.DefaultWaitSeconds,
|
||||
DefaultEmptyResponse: cfg.DefaultEmptyResponse,
|
||||
AgentStaleAfterSeconds: cfg.AgentStaleAfterSeconds,
|
||||
},
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, resp)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user