## Summary - Closes #22 - Drop CSRF cookie / `js-cookie` path; axios uses JWT `Authorization` only (`withCredentials: false`) - Unified token storage: `localStorage` sync source of truth + optional Capacitor Preferences mirror/hydrate for native shells - Request interceptor always attaches fresh bearer token; 401 refresh + sign-in redirect use hash-safe native paths - Companion backend PR: `ai_ml_operations/chat_backend` branch `capacitor-cors-csrf-22` (CORS/CSRF Capacitor origins) ## Test plan - [x] Unit tests: `tokenStorage`, `nativePlatform`, `jwtHelpers`, Auth/SignIn/WebSocket (`npm run test:ci`) - [ ] Login from Capacitor Android (`https://localhost`) and iOS (`capacitor://localhost`) - [ ] Token refresh after access expiry; logout blacklist; password reset; 401 → sign-in - [ ] Confirm browser build at `chat.aimloperations.com` unchanged - [ ] Merge companion backend PR so prod CORS includes Capacitor origins when `CORS_ORIGIN_ALLOW_ALL=false`Reviewed-on: #26
Chat Web App (chat_web_app)
Frontend for chat.aimloperations.com — a React + TypeScript single-page app for the AI ML Operations chat product.
Companion backend: chat_backend
Production serve is node-static (not Docker). Hosts build with npm into a
document root; the shared web-static nginx container from
server-infra serves
those assets. Same pattern as dta_webapp.
Stack
- React 18, TypeScript, Create React App (
react-scripts5) - Material UI (MUI) 5 + Emotion for components and theming
- React Router 6 for routing
- Axios for REST against
chat_backend - WebSocket for live chat (
REACT_APP_BACKEND_WS_API_BASE_URL) - Jest + React Testing Library for unit tests
Repository layout
chat_web_app/
├── README.md
├── .gitea/workflows/ # Unit tests + deploy-prod / deploy-beta
└── llm-fe/ # CRA application (npm root)
├── package.json
├── .env.development # Local dev API/WS URLs
├── .env.beta
├── .env.production
├── public/
└── src/
├── axiosApi.js # Axios instances (JWT + CSRF)
├── App.tsx
├── index.tsx
└── llm-fe/
├── pages/ # Route-level views
├── components/ # Shared & feature UI
├── contexts/ # Auth, account, WebSocket, theme
└── ui-kit/ # MD* design-system wrappers
Prerequisites
- Node.js 20 (matches CI)
- npm (lockfile:
llm-fe/package-lock.json) - Running chat_backend
locally for full chat/auth (default in
.env.development: port 8001)
Local setup
1. Clone and install
git clone ssh://git@git.aimloperations.com:30009/ai_ml_operations/chat_web_app.git
cd chat_web_app/llm-fe
npm ci
2. Configure environment
CRA loads mode-specific env files. Variables must be prefixed with REACT_APP_
to be visible in the browser bundle.
| File | Used when | Backend |
|---|---|---|
.env.development |
npm start |
http://127.0.0.1:8001 |
.env.production |
npm run build / build:prod |
https://chatbackend.aimloperations.com |
.env.beta |
npm run build:beta |
https://beta.chatbackend.aimloperations.com |
Required keys (already set in the committed env files):
REACT_APP_BACKEND_REST_API_BASE_URL=http://127.0.0.1:8001/api/
REACT_APP_BACKEND_WS_API_BASE_URL=ws://127.0.0.1:8001/ws/chat_again/
These are baked into the JS at build time. There is no host secret file for
this frontend (unlike Django apps under ~/Documents/secrets/). Change the
committed .env.* files if API domains change, then redeploy.
Optional local overrides (gitignored): .env.local, .env.development.local,
.env.production.local.
3. Start the backend
From the chat_backend repo (adjust to however that service is run locally):
# example — follow chat_backend README for the current command
cd chat_backend
# run Django on the port matching .env.development (8001)
4. Start the dev server
cd llm-fe
npm start
Open http://localhost:3000.
npm scripts
| Command | Purpose |
|---|---|
npm start |
CRA dev server (port 3000) |
npm run build |
Production build into llm-fe/build/ |
npm run build:prod |
Prod build + copy to /var/www/prod.chat.aimloperations/html |
npm run build:beta |
Beta env build + copy to /var/www/beta.chat.aimloperations/html |
npm test |
Jest watch mode |
npm run test:ci |
Single CI run (CI=true, no watch) |
build:prod / build:beta are intended for deploy hosts (or a machine that can
write those /var/www/... paths). Paths must match
server-infra app_catalog.chat_web_app.webroot_pattern:
/var/www/{env}.chat.aimloperations/html
Tests
cd llm-fe
npm test # watch
npm run test:ci # one-shot (matches Gitea Unit Tests)
CI / deployment
Gitea Actions workflows in .gitea/workflows/:
| Workflow | Trigger | Behavior |
|---|---|---|
unit-tests.yml |
push/PR to master |
npm ci + npm run test:ci in llm-fe/ |
deploy-prod.yml |
after green Unit Tests on master push |
server-infra/scripts/deploy.sh --app chat_web_app --env prod --ref <sha> |
deploy-beta.yml |
workflow_dispatch |
tests, then deploy --env beta |
Deploy flow (on each webserver listed in host_apps):
- Git checkout at the pinned ref under
/opt/apps/src/chat_web_app_<env> npm ciinllm-fe/npm run build:<env>→ writes the static tree under/var/www/...- web-static nginx serves that root on the host port (prod 8082, beta 8083 if enabled)
Manual deploy from the control node:
~/Documents/repos/server-infra/scripts/deploy.sh \
--app chat_web_app --env prod --ref <sha>
NPM (not managed by Ansible) should balance public chat domains to:
adama:8082+roslin:8082+ai-server-4080:8082(prod)
See server-infra IMPLEMENTATION.md.
Related repos
- Backend: chat_backend — Django API + WebSocket
- Infra: server-infra —
app_catalog,host_apps,deploy.sh, web-static - Pattern reference: dta_webapp — node-static frontend deploy