11using System . Reflection ;
2+ using System . Text . RegularExpressions ;
23using Microsoft . AspNetCore . Authorization ;
34using Microsoft . AspNetCore . Http ;
45using Microsoft . AspNetCore . Mvc ;
@@ -13,6 +14,9 @@ namespace Moonfin.Server.Api;
1314public class MoonfinWebController : ControllerBase
1415{
1516 private readonly Assembly _assembly ;
17+ private static readonly Regex BaseHrefRegex = new Regex (
18+ "<base\\ s+href=\" [^\" ]*\" \\ s*/?>" ,
19+ RegexOptions . IgnoreCase | RegexOptions . Compiled ) ;
1620 private static readonly IReadOnlyDictionary < string , string > ContentTypes =
1721 new Dictionary < string , string > ( StringComparer . OrdinalIgnoreCase )
1822 {
@@ -52,8 +56,10 @@ public MoonfinWebController()
5256 [ ProducesResponseType ( StatusCodes . Status200OK ) ]
5357 public IActionResult RedirectLegacyPluginJs ( )
5458 {
55- const string script =
56- "(function(){if(window.location.pathname.toLowerCase().indexOf('/moonfin/web/')===-1){window.location.href='/Moonfin/Web/';}})();" ;
59+ var target = ResolveWebBaseHref ( ) ;
60+ var script =
61+ "(function(){if(window.location.pathname.toLowerCase().indexOf('/moonfin/web/')===-1){window.location.href='"
62+ + target + "';}})();" ;
5763 return Content ( script , "application/javascript" ) ;
5864 }
5965
@@ -140,6 +146,11 @@ public IActionResult GetWebAsset([FromRoute] string? path)
140146
141147 if ( System . IO . File . Exists ( fullPath ) )
142148 {
149+ if ( IsIndexHtml ( fullPath ) )
150+ {
151+ return ServeIndexHtml ( fullPath ) ;
152+ }
153+
143154 return PhysicalFile ( fullPath , GetContentType ( fullPath ) ) ;
144155 }
145156
@@ -148,7 +159,7 @@ public IActionResult GetWebAsset([FromRoute] string? path)
148159 var nestedIndexPath = Path . Combine ( fullPath , "index.html" ) ;
149160 if ( System . IO . File . Exists ( nestedIndexPath ) )
150161 {
151- return PhysicalFile ( nestedIndexPath , "text/html; charset=utf-8" ) ;
162+ return ServeIndexHtml ( nestedIndexPath ) ;
152163 }
153164 }
154165
@@ -163,7 +174,7 @@ public IActionResult GetWebAsset([FromRoute] string? path)
163174 return NotFound ( new { Error = "Moonfin web entrypoint missing" , Path = indexPath } ) ;
164175 }
165176
166- return PhysicalFile ( indexPath , "text/html; charset=utf-8" ) ;
177+ return ServeIndexHtml ( indexPath ) ;
167178 }
168179
169180 private static string ? NormalizeOptionalText ( string ? value )
@@ -182,6 +193,45 @@ private string ResolveRuntimeBaseUrl()
182193 return baseUrl . TrimEnd ( '/' ) ;
183194 }
184195
196+ /// <summary>
197+ /// Resolves the absolute path the web app is served from, including any Jellyfin
198+ /// reverse-proxy sub-path prefix (Request.PathBase). Used to rewrite the index.html
199+ /// base href so the app works under both subdomain and sub-path hosting.
200+ /// </summary>
201+ private string ResolveWebBaseHref ( )
202+ {
203+ var pathBase = Request . PathBase . Value ? . TrimEnd ( '/' ) ?? string . Empty ;
204+ return $ "{ pathBase } /Moonfin/Web/";
205+ }
206+
207+ private static bool IsIndexHtml ( string filePath )
208+ {
209+ return string . Equals (
210+ Path . GetFileName ( filePath ) ,
211+ "index.html" ,
212+ StringComparison . OrdinalIgnoreCase ) ;
213+ }
214+
215+ private IActionResult ServeIndexHtml ( string indexPath )
216+ {
217+ var pathBase = Request . PathBase . Value ? . TrimEnd ( '/' ) ?? string . Empty ;
218+ if ( pathBase . Length == 0 )
219+ {
220+ // No reverse-proxy sub-path: the build-time base href is already correct,
221+ // so serve the file unchanged (identical to standard subdomain/root hosting).
222+ return PhysicalFile ( indexPath , "text/html; charset=utf-8" ) ;
223+ }
224+
225+ var html = System . IO . File . ReadAllText ( indexPath ) ;
226+ html = BaseHrefRegex . Replace ( html , _ => $ "<base href=\" { pathBase } /Moonfin/Web/\" >") ;
227+
228+ Response . Headers [ "Cache-Control" ] = "no-store, no-cache, must-revalidate" ;
229+ Response . Headers [ "Pragma" ] = "no-cache" ;
230+ Response . Headers [ "Expires" ] = "0" ;
231+
232+ return Content ( html , "text/html; charset=utf-8" ) ;
233+ }
234+
185235 private static string ResolveDiscoveryProxyUrl ( string runtimeBaseUrl )
186236 {
187237 return $ "{ runtimeBaseUrl } /Moonfin/Discovery/";
0 commit comments