Add Capacitor support with configurable API base URL#2163
Add Capacitor support with configurable API base URL#2163
Conversation
Comprehensive analysis of the chat-ui app's readiness for Capacitor (iOS/Android) builds. The SPA foundation is solid thanks to the completed "chat-ui in tauri" milestone, but a remote backend is still required. Documents blockers, browser API compatibility issues, and remaining work. https://claude.ai/code/session_01Hkh2feFJ5juwX4h2mAfYba
…ffolding - Add PUBLIC_API_BASE_URL env var and apiOrigin utility for Capacitor builds to route API calls to a configurable remote backend instead of using window.location.origin (which returns capacitor://localhost in native) - Update APIClient.ts and all 12 files with direct fetch() calls to prepend apiOrigin (empty string for web, full URL for Capacitor — zero behavior change) - Add CORS support for capacitor:// origins with OPTIONS preflight handler and Access-Control-Allow-Credentials in handle.ts - Add viewport-fit=cover meta tag and safe-area CSS insets for iOS notch - Add Capacitor scaffolding: capacitor.config.ts, cap:build/ios/android scripts, @capacitor/core and @capacitor/cli dependencies https://claude.ai/code/session_01Hkh2feFJ5juwX4h2mAfYba
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4f0d6241fb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| model = data.models[0].id; | ||
| } | ||
| const res = await fetch(`${base}/conversation`, { | ||
| const res = await fetch(`${apiOrigin}${base}/conversation`, { |
There was a problem hiding this comment.
Avoid routing legacy conversation POSTs cross-origin
With PUBLIC_API_BASE_URL set, this call is now cross-origin (/conversation on the backend origin), but CORS handling in src/lib/server/hooks/handle.ts is still scoped to ${base}/api/* (both preflight and response headers). Because this request is JSON POST, browsers preflight it and then block it without Access-Control-Allow-Origin, so creating a new conversation fails in Capacitor/native deployments.
Useful? React with 👍 / 👎.
| const token = page.url.searchParams.get("token"); | ||
|
|
||
| await fetch(`${base}/api/user/validate-token`, { | ||
| await fetch(`${apiOrigin}${base}/api/user/validate-token`, { |
There was a problem hiding this comment.
Include credentials on token validation cross-origin call
After adding apiOrigin, this request becomes cross-origin in native builds, but it still uses default fetch credentials (same-origin), so the session cookie is not sent or persisted. The validate-token handler depends on locals.sessionId from that cookie (src/routes/api/user/validate-token/+server.ts), so token validation will fail for Capacitor users even when the token is correct.
Useful? React with 👍 / 👎.
Summary
This PR adds Capacitor/native app support by making the API client configurable to work with remote backends. The changes enable the chat-ui SPA to run as a native iOS/Android app via Capacitor while maintaining full compatibility with the existing web deployment.
Key Changes
Configurable API base URL: Added
PUBLIC_API_BASE_URLenvironment variable toAPIClient.tsand createdsrc/lib/utils/apiBase.tsto centralize API origin configuration. This allows the app to point to a remote backend when running in Capacitor (e.g.,https://myserver.com) while defaulting to relative URLs for web deployments.CORS support for Capacitor: Enhanced
src/lib/server/hooks/handle.tsto:capacitor://origins in addition to configured originsAccess-Control-Allow-Credentialsheader for cookie-based authUpdated all fetch calls: Systematically updated fetch URLs across the codebase to use
apiOriginprefix:src/lib/APIClient.ts- Main API clientsrc/lib/stores/settings.ts- Settings persistencesrc/lib/stores/mcpServers.ts- MCP server managementsrc/lib/components/chat/ChatWindow.svelte- Chat operationssrc/lib/components/chat/ModelSwitch.svelte- Model switchingsrc/lib/components/chat/UrlFetchModal.svelte- URL fetchingsrc/lib/createShareLink.ts- Share link generationsrc/lib/utils/messageUpdates.ts- Message streamingsrc/routes/+layout.svelte- Token validationsrc/routes/+page.svelte- Conversation creationsrc/routes/conversation/[id]/+page.svelte- Stop generationsrc/routes/models/[...model]/+page.ts- Model subscriptionCapacitor configuration: Added
capacitor.config.tswith app metadata and build output configuration.Build scripts: Added npm scripts for Capacitor workflow:
cap:build- Build static SPA and sync with Capacitorcap:ios- Open iOS projectcap:android- Open Android projectMobile viewport improvements:
viewport-fit=coverto support notch/safe areasenv(safe-area-inset-*)) to prevent content overlap with notches and home indicatorsDependencies: Added
@capacitor/coreand@capacitor/clipackages.Documentation: Added comprehensive
CAPACITOR_READINESS.mdassessment documenting:Implementation Details
The solution maintains a hybrid architecture where Capacitor wraps the static SPA frontend, which communicates with a remote Node.js backend over HTTPS. The
apiOriginutility provides a single source of truth for API URL configuration:Build-time configuration:
The CORS handling in
handle.tsnow explicitly supports Capacitor'scapacitor://localhostorigin while maintaining security for production deployments.https://claude.ai/code/session_01Hkh2feFJ5juwX4h2mAfYba