diff --git a/allstarr/Controllers/JellyfinController.cs b/allstarr/Controllers/JellyfinController.cs index 70c8a77..cee10b3 100644 --- a/allstarr/Controllers/JellyfinController.cs +++ b/allstarr/Controllers/JellyfinController.cs @@ -1777,15 +1777,30 @@ public class JellyfinController : ControllerBase } // Ensure session capabilities are posted to Jellyfin (if not already done) + // Jellyfin automatically creates a session when the client authenticates, but we need to + // post capabilities so the session shows up in the dashboard with proper device info if (!string.IsNullOrEmpty(deviceId)) { _logger.LogInformation("🔧 Ensuring session exists for device: {DeviceId} ({Client} {Version})", deviceId, client, version); - // Post capabilities to ensure session is created - var capabilitiesEndpoint = $"Sessions/Capabilities?playableMediaTypes=Audio&supportedCommands=&supportsMediaControl=false&supportsPersistentIdentifier=true"; + // Post capabilities - Jellyfin will match this to the authenticated session by device ID + // The query parameters tell Jellyfin what this device can do + var capabilitiesEndpoint = $"Sessions/Capabilities/Full"; try { - var (capResult, capStatus) = await _proxyService.PostJsonAsync(capabilitiesEndpoint, "{}", Request.Headers); + // Send full capabilities as JSON body (more reliable than query params) + var capabilities = new + { + PlayableMediaTypes = new[] { "Audio" }, + SupportedCommands = Array.Empty(), + SupportsMediaControl = false, + SupportsPersistentIdentifier = true, + SupportsSync = false, + DeviceProfile = (object?)null // Let Jellyfin use defaults + }; + + var capabilitiesJson = JsonSerializer.Serialize(capabilities); + var (capResult, capStatus) = await _proxyService.PostJsonAsync(capabilitiesEndpoint, capabilitiesJson, Request.Headers); _logger.LogInformation("✓ Session capabilities posted ({StatusCode})", capStatus); } catch (Exception ex) @@ -1992,21 +2007,29 @@ public class JellyfinController : ControllerBase /// /// Catch-all for any other session-related requests. + /// + /// Catch-all proxy for any other session-related endpoints we haven't explicitly implemented. /// This ensures all session management calls get proxied to Jellyfin. + /// Examples: GET /Sessions, POST /Sessions/Logout, etc. /// + [HttpGet("Sessions")] + [HttpPost("Sessions")] [HttpGet("Sessions/{**path}")] [HttpPost("Sessions/{**path}")] [HttpPut("Sessions/{**path}")] [HttpDelete("Sessions/{**path}")] - public async Task ProxySessionRequest(string path) + public async Task ProxySessionRequest(string? path = null) { try { var method = Request.Method; var queryString = Request.QueryString.HasValue ? Request.QueryString.Value : ""; - var endpoint = $"Sessions/{path}{queryString}"; + var endpoint = string.IsNullOrEmpty(path) ? $"Sessions{queryString}" : $"Sessions/{path}{queryString}"; _logger.LogInformation("🔄 Proxying session request: {Method} {Endpoint}", method, endpoint); + _logger.LogDebug("Session proxy headers: {Headers}", + string.Join(", ", Request.Headers.Where(h => h.Key.Contains("Auth", StringComparison.OrdinalIgnoreCase)) + .Select(h => $"{h.Key}={h.Value}"))); // Read body if present string body = "{}"; @@ -2018,6 +2041,7 @@ public class JellyfinController : ControllerBase body = await reader.ReadToEndAsync(); } Request.Body.Position = 0; + _logger.LogDebug("Session proxy body: {Body}", body); } // Forward to Jellyfin @@ -2032,14 +2056,16 @@ public class JellyfinController : ControllerBase if (result != null) { + _logger.LogInformation("✓ Session request proxied successfully ({StatusCode})", statusCode); return new JsonResult(result.RootElement.Clone()); } + _logger.LogInformation("✓ Session request proxied ({StatusCode}, no body)", statusCode); return StatusCode(statusCode); } catch (Exception ex) { - _logger.LogError(ex, "Failed to proxy session request"); + _logger.LogError(ex, "Failed to proxy session request: {Path}", path); return StatusCode(500); } }