mirror of
https://github.com/truewhile/MeBox.git
synced 2026-09-30 19:46:38 +08:00
fix: refresh emby sessions in realtime
This commit is contained in:
@@ -52,20 +52,23 @@ func embyAuthRequiredWithSessionFallback(secret string) gin.HandlerFunc {
|
||||
|
||||
func embyRealtimeSessionActivity(svc *service.Container) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
if svc != nil && svc.Sessions != nil {
|
||||
if uid := embyUserID(c); uid != "" {
|
||||
clientInfo := embyClientInfoFromRequest(c)
|
||||
svc.Sessions.RecordActivity(c.Request.Context(), uid, embyContextUserName(c),
|
||||
clientInfo.DeviceID,
|
||||
clientInfo.DeviceName,
|
||||
clientInfo.Client,
|
||||
c.ClientIP())
|
||||
}
|
||||
}
|
||||
recordEmbySessionActivity(c, svc, embyUserID(c), embyContextUserName(c))
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func recordEmbySessionActivity(c *gin.Context, svc *service.Container, userID, userName string) {
|
||||
if c == nil || svc == nil || svc.Sessions == nil || strings.TrimSpace(userID) == "" {
|
||||
return
|
||||
}
|
||||
clientInfo := embyClientInfoFromRequest(c)
|
||||
svc.Sessions.RecordActivity(c.Request.Context(), userID, userName,
|
||||
clientInfo.DeviceID,
|
||||
clientInfo.DeviceName,
|
||||
clientInfo.Client,
|
||||
c.ClientIP())
|
||||
}
|
||||
|
||||
func embyContextUserName(c *gin.Context) string {
|
||||
if c == nil {
|
||||
return ""
|
||||
@@ -167,15 +170,25 @@ func embyClientInfoFromRequest(c *gin.Context) embyClientInfo {
|
||||
info := embyClientInfo{
|
||||
DeviceID: firstNonEmptyHeaderString(
|
||||
firstHeaderValue(c, "X-Emby-Device-Id", "X-Emby-DeviceId", "X-MediaBrowser-Device-Id", "X-MediaBrowser-DeviceId"),
|
||||
c.Query("DeviceId"),
|
||||
c.Query("DeviceID"),
|
||||
c.Query("deviceId"),
|
||||
c.Query("deviceID"),
|
||||
auth["DeviceId"],
|
||||
auth["DeviceID"],
|
||||
),
|
||||
DeviceName: firstNonEmptyHeaderString(
|
||||
firstHeaderValue(c, "X-Emby-Device-Name", "X-Emby-DeviceName", "X-MediaBrowser-Device-Name", "X-MediaBrowser-DeviceName"),
|
||||
c.Query("Device"),
|
||||
c.Query("DeviceName"),
|
||||
c.Query("device"),
|
||||
c.Query("deviceName"),
|
||||
auth["Device"],
|
||||
),
|
||||
Client: firstNonEmptyHeaderString(
|
||||
firstHeaderValue(c, "X-Emby-Client", "X-MediaBrowser-Client"),
|
||||
c.Query("Client"),
|
||||
c.Query("client"),
|
||||
auth["Client"],
|
||||
),
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/glebarez/sqlite"
|
||||
"github.com/gorilla/websocket"
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/ShukeBta/MediaStationGo/internal/model"
|
||||
@@ -179,3 +180,54 @@ func TestEmbyWebSocketRouteUpgradesForOfficialClients(t *testing.T) {
|
||||
t.Fatalf("expected websocket upgrade, got resp=%#v", resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmbyWebSocketRefreshesRealtimeActivity(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
||||
if err != nil {
|
||||
t.Fatalf("open db: %v", err)
|
||||
}
|
||||
if err := db.AutoMigrate(&model.User{}); err != nil {
|
||||
t.Fatalf("migrate: %v", err)
|
||||
}
|
||||
repos := repository.New(db)
|
||||
if err := repos.User.Create(t.Context(), &model.User{
|
||||
Base: model.Base{ID: "user-1"},
|
||||
Username: "viewer",
|
||||
PasswordHash: "x",
|
||||
Role: "admin",
|
||||
Tier: "plus",
|
||||
IsActive: true,
|
||||
}); err != nil {
|
||||
t.Fatalf("create user: %v", err)
|
||||
}
|
||||
const secret = "test-secret"
|
||||
tracker := service.NewSessionTrackerService(zap.NewNop())
|
||||
router := gin.New()
|
||||
registerEmbyRoutes(router, secret, &service.Container{
|
||||
Repo: repos,
|
||||
Sessions: tracker,
|
||||
})
|
||||
server := httptest.NewServer(router)
|
||||
defer server.Close()
|
||||
|
||||
wsURL := "ws" + strings.TrimPrefix(server.URL, "http") + "/embywebsocket?deviceId=device-1&device=Windows&client=Emby"
|
||||
header := http.Header{"X-Emby-Token": []string{signedTestToken(t, secret)}}
|
||||
conn, resp, err := websocket.DefaultDialer.Dial(wsURL, header)
|
||||
if err != nil {
|
||||
status := 0
|
||||
if resp != nil {
|
||||
status = resp.StatusCode
|
||||
}
|
||||
t.Fatalf("websocket dial failed status=%d err=%v", status, err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
sessions := tracker.List(t.Context())
|
||||
if len(sessions) != 1 {
|
||||
t.Fatalf("sessions = %#v, want websocket heartbeat session", sessions)
|
||||
}
|
||||
if sessions[0].DeviceID != "device-1" || sessions[0].DeviceName != "Windows" || sessions[0].Client != "Emby" {
|
||||
t.Fatalf("websocket did not refresh client session: %#v", sessions[0])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ func registerEmbyRoutes(r *gin.Engine, jwtSecret string, svc *service.Container)
|
||||
grp.Use(embyNoStoreHeaders())
|
||||
|
||||
registerEmbyRootRoutes(grp, prefix, svc)
|
||||
registerEmbyPublicRoutes(grp, svc)
|
||||
registerEmbyPublicRoutes(grp, jwtSecret, svc)
|
||||
registerEmbyPublicImageRoutes(grp, svc)
|
||||
|
||||
// 鉴权后端点
|
||||
@@ -46,10 +46,10 @@ func registerEmbyRootRoutes(grp *gin.RouterGroup, prefix string, svc *service.Co
|
||||
grp.HEAD("/", embyRootHandler(svc))
|
||||
}
|
||||
|
||||
func registerEmbyPublicRoutes(grp *gin.RouterGroup, svc *service.Container) {
|
||||
func registerEmbyPublicRoutes(grp *gin.RouterGroup, jwtSecret string, svc *service.Container) {
|
||||
registerEmbyPublicSystemRoutes(grp, svc)
|
||||
registerEmbyPublicSessionRoutes(grp, svc)
|
||||
registerEmbyPublicClientRoutes(grp, svc)
|
||||
registerEmbyPublicSessionRoutes(grp, jwtSecret, svc)
|
||||
registerEmbyPublicClientRoutes(grp, jwtSecret, svc)
|
||||
}
|
||||
|
||||
func registerEmbyPublicSystemRoutes(grp *gin.RouterGroup, svc *service.Container) {
|
||||
@@ -68,11 +68,13 @@ func registerEmbyPublicSystemRoutes(grp *gin.RouterGroup, svc *service.Container
|
||||
}
|
||||
}
|
||||
|
||||
func registerEmbyPublicSessionRoutes(grp *gin.RouterGroup, svc *service.Container) {
|
||||
registerEmbyPostRoutes(grp, svc, []string{
|
||||
func registerEmbyPublicSessionRoutes(grp *gin.RouterGroup, jwtSecret string, svc *service.Container) {
|
||||
for _, path := range []string{
|
||||
"/Sessions/Capabilities", "/Sessions/Capabilities/Full",
|
||||
"/sessions/capabilities", "/sessions/capabilities/full",
|
||||
}, embyNoContentHandler)
|
||||
} {
|
||||
grp.POST(path, embySessionCapabilitiesHandler(svc, jwtSecret))
|
||||
}
|
||||
|
||||
// 30/min per IP: many Emby clients sit behind a single NAT/reverse-proxy
|
||||
// IP, so a low limit would throttle legitimate logins into 429s.
|
||||
@@ -84,17 +86,19 @@ func registerEmbyPublicSessionRoutes(grp *gin.RouterGroup, svc *service.Containe
|
||||
registerEmbyGetRoutes(grp, svc, []string{"/Users/Public", "/users/public"}, embyPublicUsersHandler)
|
||||
}
|
||||
|
||||
func registerEmbyPublicClientRoutes(grp *gin.RouterGroup, svc *service.Container) {
|
||||
func registerEmbyPublicClientRoutes(grp *gin.RouterGroup, jwtSecret string, svc *service.Container) {
|
||||
registerEmbyGetRoutes(grp, svc, []string{"/Branding/Configuration", "/branding/configuration"}, embyBrandingConfigHandler)
|
||||
registerEmbyGetHeadRoutes(grp, svc, []string{"/Branding/Css", "/branding/css"}, embyBrandingCSSHandler)
|
||||
registerEmbyGetRoutes(grp, svc, []string{"/Localization/Options", "/localization/options"}, embyLocalizationOptionsHandler)
|
||||
registerEmbyGetRoutes(grp, svc, []string{"/Localization/Cultures", "/Localization/cultures", "/localization/cultures"}, embyLocalizationCulturesHandler)
|
||||
registerEmbyGetHeadRoutes(grp, svc, []string{"/CustomCssJS/Scripts", "/customcssjs/scripts"}, embyCustomCSSJSScriptsHandler)
|
||||
for _, path := range []string{"/embywebsocket", "/EmbyWebSocket"} {
|
||||
grp.GET(path, embyWebSocketHandler(svc))
|
||||
grp.GET(path, embyWebSocketHandler(svc, jwtSecret))
|
||||
grp.HEAD(path, embyNoContentHandler(svc))
|
||||
}
|
||||
registerEmbyPostRoutes(grp, svc, []string{"/Sessions/Logout", "/sessions/logout"}, embySessionLogoutHandler)
|
||||
for _, path := range []string{"/Sessions/Logout", "/sessions/logout"} {
|
||||
grp.POST(path, embySessionLogoutHandler(svc, jwtSecret))
|
||||
}
|
||||
grp.GET("/DisplayPreferences/:id", embyDisplayPreferencesHandler(svc))
|
||||
grp.POST("/DisplayPreferences/:id", embySaveDisplayPreferencesHandler(svc))
|
||||
grp.GET("/displaypreferences/:id", embyDisplayPreferencesHandler(svc))
|
||||
|
||||
@@ -228,6 +228,188 @@ func TestEmbyAuthenticatedRequestRefreshesRealtimeUserActivity(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmbySessionCapabilitiesRefreshesRealtimeActivity(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
||||
if err != nil {
|
||||
t.Fatalf("open db: %v", err)
|
||||
}
|
||||
if err := db.AutoMigrate(&model.User{}); err != nil {
|
||||
t.Fatalf("migrate: %v", err)
|
||||
}
|
||||
repos := repository.New(db)
|
||||
if err := repos.User.Create(t.Context(), &model.User{
|
||||
Base: model.Base{ID: "user-1"},
|
||||
Username: "viewer",
|
||||
PasswordHash: "x",
|
||||
Role: "admin",
|
||||
Tier: "plus",
|
||||
IsActive: true,
|
||||
}); err != nil {
|
||||
t.Fatalf("create user: %v", err)
|
||||
}
|
||||
|
||||
const secret = "test-secret"
|
||||
tracker := service.NewSessionTrackerService(zap.NewNop())
|
||||
router := gin.New()
|
||||
registerEmbyRoutes(router, secret, &service.Container{
|
||||
Repo: repos,
|
||||
Sessions: tracker,
|
||||
})
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/emby/Sessions/Capabilities/Full?deviceId=phone-1&device=iPhone&client=Infuse", strings.NewReader(`{}`))
|
||||
req.Header.Set("X-Emby-Token", signedTestToken(t, secret))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusNoContent {
|
||||
t.Fatalf("capabilities status: %d body=%s", w.Code, w.Body.String())
|
||||
}
|
||||
sessions := tracker.List(t.Context())
|
||||
if len(sessions) != 1 {
|
||||
t.Fatalf("sessions = %#v, want one heartbeat session", sessions)
|
||||
}
|
||||
if sessions[0].DeviceID != "phone-1" || sessions[0].DeviceName != "iPhone" || sessions[0].Client != "Infuse" || sessions[0].UserName != "viewer" {
|
||||
t.Fatalf("capabilities did not refresh client session: %#v", sessions[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmbySessionCapabilitiesIgnoresScopedPlaybackToken(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
||||
if err != nil {
|
||||
t.Fatalf("open db: %v", err)
|
||||
}
|
||||
if err := db.AutoMigrate(&model.User{}); err != nil {
|
||||
t.Fatalf("migrate: %v", err)
|
||||
}
|
||||
repos := repository.New(db)
|
||||
if err := repos.User.Create(t.Context(), &model.User{
|
||||
Base: model.Base{ID: "user-1"},
|
||||
Username: "viewer",
|
||||
PasswordHash: "x",
|
||||
Role: "admin",
|
||||
Tier: "plus",
|
||||
IsActive: true,
|
||||
}); err != nil {
|
||||
t.Fatalf("create user: %v", err)
|
||||
}
|
||||
|
||||
const secret = "test-secret"
|
||||
tracker := service.NewSessionTrackerService(zap.NewNop())
|
||||
router := gin.New()
|
||||
registerEmbyRoutes(router, secret, &service.Container{
|
||||
Repo: repos,
|
||||
Sessions: tracker,
|
||||
})
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/emby/Sessions/Capabilities/Full?deviceId=phone-1&device=iPhone&client=Infuse", strings.NewReader(`{}`))
|
||||
req.Header.Set("X-Emby-Token", signedTestTokenWithPurpose(t, secret, service.ExternalPlaybackTokenPurpose))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusNoContent {
|
||||
t.Fatalf("capabilities status: %d body=%s", w.Code, w.Body.String())
|
||||
}
|
||||
if sessions := tracker.List(t.Context()); len(sessions) != 0 {
|
||||
t.Fatalf("scoped external playback token must not create realtime session: %#v", sessions)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmbyLogoutRemovesRealtimeSessionFromPublicRoute(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
||||
if err != nil {
|
||||
t.Fatalf("open db: %v", err)
|
||||
}
|
||||
if err := db.AutoMigrate(&model.User{}); err != nil {
|
||||
t.Fatalf("migrate: %v", err)
|
||||
}
|
||||
repos := repository.New(db)
|
||||
if err := repos.User.Create(t.Context(), &model.User{
|
||||
Base: model.Base{ID: "user-1"},
|
||||
Username: "viewer",
|
||||
PasswordHash: "x",
|
||||
Role: "admin",
|
||||
Tier: "plus",
|
||||
IsActive: true,
|
||||
}); err != nil {
|
||||
t.Fatalf("create user: %v", err)
|
||||
}
|
||||
|
||||
const secret = "test-secret"
|
||||
tracker := service.NewSessionTrackerService(zap.NewNop())
|
||||
tracker.RecordActivity(t.Context(), "user-1", "viewer", "phone-1", "iPhone", "Infuse", "192.0.2.10")
|
||||
tracker.RecordActivity(t.Context(), "user-1", "viewer", "tv-1", "Apple TV", "Yamby", "192.0.2.11")
|
||||
router := gin.New()
|
||||
registerEmbyRoutes(router, secret, &service.Container{
|
||||
Repo: repos,
|
||||
Sessions: tracker,
|
||||
})
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/emby/Sessions/Logout", nil)
|
||||
req.Header.Set("X-MediaBrowser-Authorization", `MediaBrowser Client="Infuse", Device="iPhone", DeviceId="phone-1", Token="`+signedTestToken(t, secret)+`"`)
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusNoContent {
|
||||
t.Fatalf("logout status: %d body=%s", w.Code, w.Body.String())
|
||||
}
|
||||
sessions := tracker.List(t.Context())
|
||||
if len(sessions) != 1 {
|
||||
t.Fatalf("sessions after logout = %#v, want only the other device", sessions)
|
||||
}
|
||||
if sessions[0].DeviceID != "tv-1" {
|
||||
t.Fatalf("remaining session = %#v, want tv-1", sessions[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmbyLogoutIgnoresScopedPlaybackToken(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
||||
if err != nil {
|
||||
t.Fatalf("open db: %v", err)
|
||||
}
|
||||
if err := db.AutoMigrate(&model.User{}); err != nil {
|
||||
t.Fatalf("migrate: %v", err)
|
||||
}
|
||||
repos := repository.New(db)
|
||||
if err := repos.User.Create(t.Context(), &model.User{
|
||||
Base: model.Base{ID: "user-1"},
|
||||
Username: "viewer",
|
||||
PasswordHash: "x",
|
||||
Role: "admin",
|
||||
Tier: "plus",
|
||||
IsActive: true,
|
||||
}); err != nil {
|
||||
t.Fatalf("create user: %v", err)
|
||||
}
|
||||
|
||||
const secret = "test-secret"
|
||||
tracker := service.NewSessionTrackerService(zap.NewNop())
|
||||
tracker.RecordActivity(t.Context(), "user-1", "viewer", "phone-1", "iPhone", "Infuse", "192.0.2.10")
|
||||
router := gin.New()
|
||||
registerEmbyRoutes(router, secret, &service.Container{
|
||||
Repo: repos,
|
||||
Sessions: tracker,
|
||||
})
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/emby/Sessions/Logout", nil)
|
||||
req.Header.Set("X-MediaBrowser-Authorization", `MediaBrowser Client="Infuse", Device="iPhone", DeviceId="phone-1", Token="`+signedTestTokenWithPurpose(t, secret, service.ExternalPlaybackTokenPurpose)+`"`)
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusNoContent {
|
||||
t.Fatalf("logout status: %d body=%s", w.Code, w.Body.String())
|
||||
}
|
||||
sessions := tracker.List(t.Context())
|
||||
if len(sessions) != 1 || sessions[0].DeviceID != "phone-1" {
|
||||
t.Fatalf("scoped external playback token must not logout realtime session: %#v", sessions)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmbyUppercaseSessionCapabilitiesRouteNoContent(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
||||
|
||||
@@ -2,9 +2,12 @@ package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
|
||||
"github.com/ShukeBta/MediaStationGo/internal/middleware"
|
||||
"github.com/ShukeBta/MediaStationGo/internal/service"
|
||||
)
|
||||
|
||||
@@ -47,13 +50,58 @@ func embySessionsHandler(svc *service.Container) gin.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func embySessionLogoutHandler(svc *service.Container) gin.HandlerFunc {
|
||||
func embySessionLogoutHandler(svc *service.Container, jwtSecret string) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
if svc.Sessions != nil {
|
||||
uid := embyUserID(c)
|
||||
uid, _ := embyPublicSessionIdentity(c, svc, jwtSecret)
|
||||
clientInfo := embyClientInfoFromRequest(c)
|
||||
svc.Sessions.Logout(c.Request.Context(), uid, clientInfo.DeviceID, c.ClientIP())
|
||||
}
|
||||
c.Status(http.StatusNoContent)
|
||||
}
|
||||
}
|
||||
|
||||
func embySessionCapabilitiesHandler(svc *service.Container, jwtSecret string) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
recordEmbyPublicSessionActivity(c, svc, jwtSecret)
|
||||
c.Status(http.StatusNoContent)
|
||||
}
|
||||
}
|
||||
|
||||
func recordEmbyPublicSessionActivity(c *gin.Context, svc *service.Container, jwtSecret string) {
|
||||
uid, username := embyPublicSessionIdentity(c, svc, jwtSecret)
|
||||
recordEmbySessionActivity(c, svc, uid, username)
|
||||
}
|
||||
|
||||
func embyPublicSessionIdentity(c *gin.Context, svc *service.Container, jwtSecret string) (string, string) {
|
||||
if uid := embyUserID(c); uid != "" {
|
||||
return uid, embyContextUserName(c)
|
||||
}
|
||||
token := embyRequestToken(c)
|
||||
if strings.TrimSpace(token) == "" || strings.TrimSpace(jwtSecret) == "" {
|
||||
return "", ""
|
||||
}
|
||||
claims := &middleware.Claims{}
|
||||
parsed, err := jwt.ParseWithClaims(token, claims, func(t *jwt.Token) (interface{}, error) {
|
||||
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, jwt.ErrTokenSignatureInvalid
|
||||
}
|
||||
return []byte(jwtSecret), nil
|
||||
})
|
||||
if err != nil || !parsed.Valid {
|
||||
return "", ""
|
||||
}
|
||||
if strings.TrimSpace(claims.Purpose) != "" {
|
||||
return "", ""
|
||||
}
|
||||
uid := strings.TrimSpace(claims.UserID)
|
||||
if uid == "" {
|
||||
return "", ""
|
||||
}
|
||||
if svc != nil && svc.Repo != nil && svc.Repo.User != nil {
|
||||
if user, err := svc.Repo.User.FindByID(c.Request.Context(), uid); err == nil && user != nil {
|
||||
return uid, user.Username
|
||||
}
|
||||
}
|
||||
return uid, ""
|
||||
}
|
||||
|
||||
@@ -16,8 +16,9 @@ func embyNoContentHandler(_ *service.Container) gin.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func embyWebSocketHandler(_ *service.Container) gin.HandlerFunc {
|
||||
func embyWebSocketHandler(svc *service.Container, jwtSecret string) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
recordEmbyPublicSessionActivity(c, svc, jwtSecret)
|
||||
if !websocket.IsWebSocketUpgrade(c.Request) {
|
||||
c.Status(http.StatusNoContent)
|
||||
return
|
||||
@@ -45,6 +46,7 @@ func embyWebSocketHandler(_ *service.Container) gin.HandlerFunc {
|
||||
case <-done:
|
||||
return
|
||||
case <-ticker.C:
|
||||
recordEmbyPublicSessionActivity(c, svc, jwtSecret)
|
||||
_ = conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
|
||||
if err := conn.WriteMessage(websocket.PingMessage, nil); err != nil {
|
||||
return
|
||||
|
||||
@@ -10,11 +10,17 @@ import (
|
||||
)
|
||||
|
||||
func signedTestToken(t *testing.T, secret string) string {
|
||||
t.Helper()
|
||||
return signedTestTokenWithPurpose(t, secret, "")
|
||||
}
|
||||
|
||||
func signedTestTokenWithPurpose(t *testing.T, secret, purpose string) string {
|
||||
t.Helper()
|
||||
claims := middleware.Claims{
|
||||
UserID: "user-1",
|
||||
Role: "admin",
|
||||
Tier: "plus",
|
||||
UserID: "user-1",
|
||||
Role: "admin",
|
||||
Tier: "plus",
|
||||
Purpose: purpose,
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)),
|
||||
Issuer: "mediastationgo-test",
|
||||
|
||||
Reference in New Issue
Block a user