fix: session capabilities using disposed HTTP context

- Extract AccessToken from auth response before background task
- Create new HeaderDictionary with token instead of using Request.Headers
- Prevents ObjectDisposedException when HTTP context is disposed
- Session capabilities now work correctly for all clients

Note: WebSocket support for external tracks already implemented via
JellyfinSessionManager.EnsureSessionAsync and WebSocketProxyMiddleware
This commit is contained in:
2026-02-07 11:49:43 -05:00
parent da8cb29e08
commit 6169d7a4ac

View File

@@ -1838,17 +1838,35 @@ public class JellyfinController : ControllerBase
{ {
var responseJson = result.RootElement.GetRawText(); var responseJson = result.RootElement.GetRawText();
// On successful auth, post session capabilities in background // On successful auth, extract access token and post session capabilities in background
if (statusCode == 200) if (statusCode == 200)
{ {
_logger.LogInformation("Authentication successful, posting session capabilities in background"); _logger.LogInformation("Authentication successful");
// Don't await - do this in background so we return auth response immediately // Extract access token from response for session capabilities
string? accessToken = null;
if (result.RootElement.TryGetProperty("AccessToken", out var tokenEl))
{
accessToken = tokenEl.GetString();
}
// Post session capabilities in background if we have a token
if (!string.IsNullOrEmpty(accessToken))
{
// Capture token in closure - don't use Request.Headers (will be disposed)
var token = accessToken;
_ = Task.Run(async () => _ = Task.Run(async () =>
{ {
try try
{ {
_logger.LogInformation("🔧 Posting session capabilities after authentication"); _logger.LogDebug("🔧 Posting session capabilities after authentication");
// Build auth header with the new token
var authHeaders = new HeaderDictionary
{
["X-Emby-Token"] = token
};
var capabilities = new var capabilities = new
{ {
PlayableMediaTypes = new[] { "Audio" }, PlayableMediaTypes = new[] { "Audio" },
@@ -1859,23 +1877,24 @@ public class JellyfinController : ControllerBase
}; };
var capabilitiesJson = JsonSerializer.Serialize(capabilities); var capabilitiesJson = JsonSerializer.Serialize(capabilities);
var (capResult, capStatus) = await _proxyService.PostJsonAsync("Sessions/Capabilities/Full", capabilitiesJson, Request.Headers); var (capResult, capStatus) = await _proxyService.PostJsonAsync("Sessions/Capabilities/Full", capabilitiesJson, authHeaders);
if (capStatus == 204 || capStatus == 200) if (capStatus == 204 || capStatus == 200)
{ {
_logger.LogInformation("✓ Session capabilities posted after auth ({StatusCode})", capStatus); _logger.LogDebug("✓ Session capabilities posted after auth ({StatusCode})", capStatus);
} }
else else
{ {
_logger.LogWarning("⚠ Session capabilities returned {StatusCode} after auth", capStatus); _logger.LogDebug("⚠ Session capabilities returned {StatusCode} after auth", capStatus);
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogWarning(ex, "Failed to post session capabilities after auth"); _logger.LogDebug(ex, "Failed to post session capabilities after auth");
} }
}); });
} }
}
else else
{ {
_logger.LogWarning("Authentication failed - status {StatusCode}", statusCode); _logger.LogWarning("Authentication failed - status {StatusCode}", statusCode);