Repository navigation
fix(web-ui): resolve the base path at runtime for reverse proxies - #6096
caioocesar wants to merge 1 commit into
Conversation
|
The single page application refactor switched Vite to `base: '/'` and added `<base href="/">`, so assets, API requests and routes always resolve against the domain root. Behind a reverse proxy that serves the UI under a sub-path (e.g. /sunshine/), the browser requests /assets/... and the UI fails to load. Resolve the base path from the current URL in a small inline script, build assets relative to it, and make the remaining root-absolute API URLs relative. vue-router picks the base up from the <base> element. Fixes SunshineStream#6048 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ReenigneArcher
left a comment
There was a problem hiding this comment.
Reviewed bdd7b52e2d1a1c40a76ad4fa76e4f50858126c44. Requesting changes for the two base-path issues below. The original root-relative base is still present on current master, so #6048 remains relevant.
Validation: executed the actual inline script in an isolated Node harness and checked resulting asset URLs, including ordinary paths, route-name mount prefixes and repeated leading slashes. I did not run the full Vitest suite, a Vite build, or a real Sunshine/nginx deployment. CI, CodeQL and common lint await maintainer approval; that is a verification limit, not a contributor defect. GitHub currently reports the PR as mergeable without conflicts.
Review completed by Codex | Model: unavailable | Thinking: unavailable
| segments.pop(); | ||
| } | ||
| var base = document.createElement('base'); | ||
| base.href = segments.join('/') + '/'; |
There was a problem hiding this comment.
[P1] Keep the generated base on the document's origin
If the pathname starts with repeated slashes, this assignment can produce a network-path reference rather than a root-relative path. Relative built scripts, styles and API URLs then resolve against a different origin. I verified the inline script's output and URL resolution, not an end-to-end exploit or the deployed server/proxy's normalization. The previous fixed / base did not have this behavior.
Ensure the output has exactly one leading slash before assigning it. This focused guard preserves the ordinary / and /sunshine/config cases in an isolated script check:
| base.href = segments.join('/') + '/'; | |
| base.href = ('/' + segments.join('/') + '/').replace(/^\/+/, '/'); |
Also add a regression in tests/web/base_path.test.js that exercises repeated leading slashes and asserts that resolving the returned base against a document URL preserves its origin. Run npx vitest run tests/web/base_path.test.js. The suggestion has not been tested in a real Sunshine/proxy deployment and does not address the separate prefix ambiguity.
Review completed by Codex | Model: unavailable | Thinking: unavailable
| // A route visited with a trailing slash (for example /logout/) leaves its name as the last segment. | ||
| if (segments.length > 1 && routes.indexOf(segments[segments.length - 1]) !== -1) { | ||
| segments.pop(); |
There was a problem hiding this comment.
[P2] Do not discard a proxy prefix just because it matches a route
For a proxy mounted at /apps/, both /apps/ and /apps/config produce base /; for a proxy mounted at /proxy/config/, both /proxy/config/ and /proxy/config/apps produce /proxy/. Assets and API calls therefore leave the configured mount and the UI still fails to load. I reproduced these outputs with this inline script. These are valid mount prefixes and known routes, not the documented unknown-path limitation.
The same pathname can represent either a direct route or a proxy mount, so a route-name heuristic cannot distinguish every case. Please provide an explicit base-prefix override (or another unambiguous source of the mount prefix) rather than stripping a matching segment unconditionally. Once that input is available, extend tests/web/base_path.test.js to cover configured /apps/ and /proxy/config/ mounts at their roots and at /config or /apps respectively, while retaining direct /apps/ and /logout/ coverage. Assert both the selected base and that an asset/API URL stays under it. A directly applicable one-line replacement would be misleading here because the required prefix input is not present yet.
Review completed by Codex | Model: unavailable | Thinking: unavailable
Description
Since the single page application refactor (#5731), the web UI is built with
base: '/'andindex.htmlhard-codes<base href="/" />. Assets, API requests and router links therefore always resolve against the domain root. When a reverse proxy serves the UI under a sub-path (for examplehttps://example.com/sunshine/→https://host:47990/), the browser requestshttps://example.com/assets/index-*.jsand the UI does not load. The multi-page UI supported this through relative paths (#3173).Changes:
index.html: replace the fixed<base href="/" />with a small inline script that derives the base path from the current URL. It drops the last path segment and, for routes visited with a trailing slash (the server accepts/logout/and/welcome/), also drops the known route name. Examples:/→/,/config→/,/logout/→/,/sunshine/→/sunshine/,/sunshine/troubleshooting/→/sunshine/.vite.config.js:base: './', so the builtindex.htmlreferences./assets/...and./images/..., which resolve against that<base>. Vite already rewrites public image URLs used in templates relative to the bundle.createWebHistory()without an argument reads the base from the<base>element.Troubleshooting.vue, the cover image inApps.vue) and the logout URL and request inNavbar.vueare now relative to the base.router.jsexportsroutesso a test can check that the route list inindex.htmlstays in sync.Without a proxy, URLs resolve exactly as before for every route.
Known limitations, outside this frontend-only change:
Locationheaders (/welcomeand/, inconfighttp.cpp). A sub-path proxy has to rewrite them, for example with nginxproxy_redirect / /sunshine/;./foo/bar) now resolves the base to/foo/and the assets do not load. Before, the router redirected it to/. Unknown single-segment paths and unknown paths under a route (e.g./apps/foo) still resolve to/.Screenshot
No visual change.
Issues Fixed or Closed
Roadmap Issues
Testing
tests/web/base_path.test.js(13 tests). It runs the inline script fromindex.htmlagainst 10 paths, with and without a sub-path. It also checks that the script's route list matches the top-level routes inrouter.js, thatindex.htmlno longer hard-codes a<base>element, and that no bundled source requests/api/...from the domain root.tests/web/Troubleshooting.test.js: the expected URLs now use./api/....base_path.test.jsandTroubleshooting.test.js. With the fix they pass.npx vitest runon Windows: 229 passed, 1 failed. The failure isExternalAudio.test.js› "provides the standard documentation table…". It also fails on an unmodified checkout on Windows and appears to come from CRLF line endings indocs/configuration.md, so it is unrelated.npx vite build: succeeds. The generatedindex.htmlcontains the inline script and references./assets/index-*.js,./assets/index-*.cssand./images/sunshine.ico. The navbar logo compiles tonew URL('../images/logo-sunshine-45.png', import.meta.url).confighttp.cpprouting: explicit/api,/assetsand/imagesroutes,/logout/?and/welcome/?, and the GET fallback toindex.htmlwith 404 under/api,/assetsand/images. A second Node server proxied it under/sunshine/and/a/b/sunshine/, like nginxlocation /sunshine/ { proxy_pass http://backend/; }. Results with this branch:/,/config,/welcome,/unknown,/troubleshooting/,/logout/and/apps/unknownload. The UI mounts and the API calls go to/api/..../sunshine/,/sunshine/config,/sunshine/apps,/sunshine/pin,/sunshine/unknown,/sunshine/troubleshooting/,/sunshine/logout/,/sunshine/apps/unknown,/a/b/sunshine/and/a/b/sunshine/troubleshootingload. Assets, the navbar logo, app covers and every API call stay under the prefix./sunshine/and the redirect to/sunshine/logout) and the logout page's login link all stay under the prefix.masterbuild loads directly, but behind the proxy every page fails with 404 on/assets/index-*.jsand/assets/index-*.css, which reproduces web-ui not requesting relative path for reverse proxy purpose #6048./logout/), Chrome's preload scanner requests<route>/assets/index-*.jsonce before the inline script inserts<base>. The server answers withindex.html, the browser discards it, and the module then loads from the correct path. The page works, at the cost of one extra request./foo/bardoes not load with this branch (base/foo/), whilemasterredirects it to/. A proxied path without a trailing slash (/sunshine) also resolves to base/. nginx redirects/sunshineto/sunshine/for alocation /sunshine/block.Type of Change
Checklist
AI Usage
See our AI usage policy.
The code, tests and this description were written by an AI agent (Claude Code) and published from my account.
🤖 Generated with Claude Code