mirror of
https://github.com/navidrome/navidrome.git
synced 2026-10-29 06:37:17 +01:00
56 lines
1.9 KiB
Go
56 lines
1.9 KiB
Go
package jellyfin
|
|
|
|
import (
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/gorilla/websocket"
|
|
"github.com/navidrome/navidrome/log"
|
|
)
|
|
|
|
// socketKeepAliveInterval is sent to the client in the initial ForceKeepAlive message, telling it
|
|
// how often (in seconds) it must send a KeepAlive, and used locally to bound the read deadline.
|
|
const socketKeepAliveInterval = 60
|
|
|
|
// socketReadTimeout is generous relative to socketKeepAliveInterval so a single delayed
|
|
// KeepAlive doesn't drop the connection.
|
|
const socketReadTimeout = 90 * time.Second
|
|
|
|
var socketUpgrader = websocket.Upgrader{
|
|
// Finamp (and other Jellyfin clients) are not browsers, so there's no cross-origin risk
|
|
// to guard against here; the connection is already authenticated via api_key.
|
|
CheckOrigin: func(*http.Request) bool { return true },
|
|
}
|
|
|
|
// handleSocket implements Jellyfin's /socket WebSocket endpoint. Real-time clients like Finamp
|
|
// open this right after login; without it they 404-loop-reconnect instead of ever settling into
|
|
// a working session. This is a minimal implementation: it just keeps the connection alive and
|
|
// answers KeepAlive pings, with no session/playstate push, which is enough to stop the loop.
|
|
func (api *Router) handleSocket(w http.ResponseWriter, r *http.Request) {
|
|
conn, err := socketUpgrader.Upgrade(w, r, nil)
|
|
if err != nil {
|
|
log.Warn(r.Context(), "Jellyfin API: WebSocket upgrade failed", err)
|
|
return
|
|
}
|
|
defer conn.Close()
|
|
|
|
if err := conn.WriteJSON(map[string]any{"MessageType": "ForceKeepAlive", "Data": socketKeepAliveInterval}); err != nil {
|
|
log.Warn(r.Context(), "Jellyfin API: WebSocket failed to send ForceKeepAlive", err)
|
|
return
|
|
}
|
|
|
|
for {
|
|
_ = conn.SetReadDeadline(time.Now().Add(socketReadTimeout))
|
|
var msg struct {
|
|
MessageType string `json:"MessageType"`
|
|
}
|
|
if err := conn.ReadJSON(&msg); err != nil {
|
|
return
|
|
}
|
|
if msg.MessageType == "KeepAlive" {
|
|
if err := conn.WriteJSON(map[string]any{"MessageType": "KeepAlive"}); err != nil {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|