Repository navigation
fix(react-client): prefix configured asset URLs with the app base path - #3064
Bierbarbar wants to merge 1 commit into
Conversation
logo_file_url, default_avatar_file_url, ChatProfile.icon, and
login_page_image were returned verbatim when set to a /public/... path,
bypassing apiClient.buildEndpoint() and breaking under a reverse-proxy
subpath (window.origin + basename never gets prepended). Several
sibling call sites (WelcomeScreen, Starter, ChatProfiles) already guard
with `startsWith('/public') ? buildEndpoint(url) : url` — this applies
the same guard to the three places that were missing it.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
There was a problem hiding this comment.
2 issues found across 4 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="libs/react-client/src/api/index.tsx">
<violation number="1" location="libs/react-client/src/api/index.tsx:353">
P3: `startsWith('/public')` matches any path whose first segment merely starts with those characters (e.g. `/public_html/logo.png`, `/Public/logo.png`), which then gets rewritten through `buildEndpoint()` while sibling paths are left untouched. Use the documented `/public/` boundary, and consider extracting a shared helper since the identical check is duplicated in `frontend/src/components/chat/Messages/Message/Avatar.tsx` and `frontend/src/pages/Login.tsx`.</violation>
</file>
<file name="frontend/src/pages/Login.tsx">
<violation number="1" location="frontend/src/pages/Login.tsx:110">
P3: The `startsWith('/public') ? apiClient.buildEndpoint(url) : url` resolution is now duplicated in at least three places in this PR (Login.tsx, Avatar.tsx, react-client getLogoEndpoint) on top of the pre-existing copies in ButtonLink.tsx, Markdown.tsx, ModePicker.tsx, Starter.tsx, WelcomeScreen.tsx and ChatProfiles.tsx. Since the react-client library already owns `buildEndpoint`, add one shared helper there (e.g. `buildAssetEndpoint(url)`) and call it from every site so the prefix rule stays in a single place; this also removes Login's 3-level nested ternary.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| getLogoEndpoint(theme: string, configuredLogoUrl?: string) { | ||
| if (configuredLogoUrl) return configuredLogoUrl; | ||
| if (configuredLogoUrl) { | ||
| return configuredLogoUrl.startsWith('/public') |
There was a problem hiding this comment.
P3: startsWith('/public') matches any path whose first segment merely starts with those characters (e.g. /public_html/logo.png, /Public/logo.png), which then gets rewritten through buildEndpoint() while sibling paths are left untouched. Use the documented /public/ boundary, and consider extracting a shared helper since the identical check is duplicated in frontend/src/components/chat/Messages/Message/Avatar.tsx and frontend/src/pages/Login.tsx.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At libs/react-client/src/api/index.tsx, line 353:
<comment>`startsWith('/public')` matches any path whose first segment merely starts with those characters (e.g. `/public_html/logo.png`, `/Public/logo.png`), which then gets rewritten through `buildEndpoint()` while sibling paths are left untouched. Use the documented `/public/` boundary, and consider extracting a shared helper since the identical check is duplicated in `frontend/src/components/chat/Messages/Message/Avatar.tsx` and `frontend/src/pages/Login.tsx`.</comment>
<file context>
@@ -349,7 +349,11 @@ export class ChainlitAPI extends APIBase {
getLogoEndpoint(theme: string, configuredLogoUrl?: string) {
- if (configuredLogoUrl) return configuredLogoUrl;
+ if (configuredLogoUrl) {
+ return configuredLogoUrl.startsWith('/public')
+ ? this.buildEndpoint(configuredLogoUrl)
+ : configuredLogoUrl;
</file context>
| src={ | ||
| config?.ui?.login_page_image || | ||
| apiClient.buildEndpoint('/favicon') | ||
| config?.ui?.login_page_image |
There was a problem hiding this comment.
P3: The startsWith('/public') ? apiClient.buildEndpoint(url) : url resolution is now duplicated in at least three places in this PR (Login.tsx, Avatar.tsx, react-client getLogoEndpoint) on top of the pre-existing copies in ButtonLink.tsx, Markdown.tsx, ModePicker.tsx, Starter.tsx, WelcomeScreen.tsx and ChatProfiles.tsx. Since the react-client library already owns buildEndpoint, add one shared helper there (e.g. buildAssetEndpoint(url)) and call it from every site so the prefix rule stays in a single place; this also removes Login's 3-level nested ternary.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At frontend/src/pages/Login.tsx, line 110:
<comment>The `startsWith('/public') ? apiClient.buildEndpoint(url) : url` resolution is now duplicated in at least three places in this PR (Login.tsx, Avatar.tsx, react-client getLogoEndpoint) on top of the pre-existing copies in ButtonLink.tsx, Markdown.tsx, ModePicker.tsx, Starter.tsx, WelcomeScreen.tsx and ChatProfiles.tsx. Since the react-client library already owns `buildEndpoint`, add one shared helper there (e.g. `buildAssetEndpoint(url)`) and call it from every site so the prefix rule stays in a single place; this also removes Login's 3-level nested ternary.</comment>
<file context>
@@ -107,8 +107,11 @@ export default function Login() {
src={
- config?.ui?.login_page_image ||
- apiClient.buildEndpoint('/favicon')
+ config?.ui?.login_page_image
+ ? config.ui.login_page_image.startsWith('/public')
+ ? apiClient.buildEndpoint(config.ui.login_page_image)
</file context>
Summary
logo_file_url,default_avatar_file_url,ChatProfile.icon, andlogin_page_imageare returned verbatim by the frontend whenever they're configured as a/public/...-relative path, bypassingapiClient.buildEndpoint()entirely. This breaks under a reverse-proxy subpath deployment (e.g.https://host/my-app/...): the browser requests the unprefixed path against the domain root, which never reaches the app.Several sibling call sites already guard against exactly this with the same pattern:
(see
WelcomeScreen.tsx,Starter.tsx,ChatProfiles.tsx,ModePicker.tsx,ButtonLink.tsx) —buildEndpoint()is what correctly prependshttpEndpoint(window.origin + basename), so an already-absolute external URL is left untouched while a local/public/...path gets the app's base path prepended.This PR applies the same, already-established guard to the three places that were missing it:
ChainlitAPI.getLogoEndpoint()(libs/react-client/src/api/index.tsx) — returnedconfiguredLogoUrlrawAvatar.tsx— returneddefault_avatar_file_urlandselectedChatProfile.iconrawLogin.tsx— returnedlogin_page_imagerawNo new abstraction, no behavior change for apps not deployed behind a subpath (an unprefixed
httpEndpointis a no-op prefix).Test plan
libs/react-client/src/api/index.spec.tscoveringgetLogoEndpoint: prefixes a/public/...path, leaves an external URL untouched, and the unconfigured fallback still works. Matches the existing unit-test convention in this package (state.spec.ts) — the project otherwise relies on Cypress E2E rather than component unit tests, and adding an E2E fixture for this would have required changes to the shared test runner (no per-scenario env var support today), which felt like scope creep for a 3-site fix.pnpm lint,pnpm format-check,pnpm type-check(whole repo) all pass.pnpm testinlibs/react-client: 19/19 passing.Reopens #3036, which was auto-closed by the stale bot for inactivity (no unresolved review feedback).
🤖 Generated with Claude Code
Summary by cubic
Fixes configured asset URLs (
logo_file_url,default_avatar_file_url,ChatProfile.icon,login_page_image) so/public/...paths get the app base path prepended, preventing 404s under reverse-proxy subpath deployments. External URLs are left untouched.buildEndpoint()guard already used by sibling call sites togetLogoEndpoint(),Avatar.tsx, andLogin.tsx.getLogoEndpointcovering prefixed, external, and fallback cases.Written for commit d65fc9e. Summary will update on new commits.