diff --git a/frontend/Dockerfile b/frontend/Dockerfile index a596499..b05cba1 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -12,10 +12,9 @@ RUN npm run build FROM nginx:alpine WORKDIR /usr/share/nginx/html -COPY nginx.conf /etc/nginx/conf.d/default.conf COPY --from=build /app/dist ./ -ENV API_BASE_URL="" +ENV API_PROXY_PASS="" COPY docker-entrypoint.sh /docker-entrypoint.sh RUN chmod +x /docker-entrypoint.sh diff --git a/frontend/docker-entrypoint.sh b/frontend/docker-entrypoint.sh index 2d86f2b..5209f5d 100644 --- a/frontend/docker-entrypoint.sh +++ b/frontend/docker-entrypoint.sh @@ -1,11 +1,37 @@ #!/bin/sh set -euo pipefail -API_BASE_URL_TRIMMED="${API_BASE_URL:-}" -API_BASE_URL_TRIMMED="${API_BASE_URL_TRIMMED%%/}" +API_PROXY_PASS_TRIMMED="${API_PROXY_PASS:-}" +API_PROXY_PASS_TRIMMED="${API_PROXY_PASS_TRIMMED%%/}" -cat < /usr/share/nginx/html/config.js -window.__PAPERCRATE_API_BASE_URL = "${API_BASE_URL_TRIMMED}"; -CONFIG +cat <<'BASE' > /etc/nginx/conf.d/default.conf +server { + listen 80; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + location / { + try_files $uri /index.html; + } +BASE + +if [ -n "$API_PROXY_PASS_TRIMMED" ]; then +cat <> /etc/nginx/conf.d/default.conf + + location /api/ { + proxy_pass ${API_PROXY_PASS_TRIMMED}; + proxy_set_header Host \$host; + proxy_set_header X-Real-IP \$remote_addr; + proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto \$scheme; + } +PROXY +fi + +cat <<'ENDCFG' >> /etc/nginx/conf.d/default.conf +} +ENDCFG exec "$@" diff --git a/frontend/nginx.conf b/frontend/nginx.conf deleted file mode 100644 index 9ba98f1..0000000 --- a/frontend/nginx.conf +++ /dev/null @@ -1,11 +0,0 @@ -server { - listen 80; - server_name _; - - root /usr/share/nginx/html; - index index.html; - - location / { - try_files $uri /index.html; - } -} diff --git a/frontend/public/config.js b/frontend/public/config.js deleted file mode 100644 index f6a4f62..0000000 --- a/frontend/public/config.js +++ /dev/null @@ -1 +0,0 @@ -window.__PAPERCRATE_API_BASE_URL = window.__PAPERCRATE_API_BASE_URL || ''; diff --git a/frontend/src/index.html b/frontend/src/index.html index 1a26e61..d5cc24a 100644 --- a/frontend/src/index.html +++ b/frontend/src/index.html @@ -6,7 +6,6 @@ Papercrate -
diff --git a/frontend/src/index.jsx b/frontend/src/index.jsx index 88345c3..d949eae 100644 --- a/frontend/src/index.jsx +++ b/frontend/src/index.jsx @@ -36,33 +36,28 @@ import { createPreviewSurface } from './preview/PreviewWorkspace'; import { createDesktopSurface } from './DesktopWorkspace'; import { AppShellContext, useAppShell } from './appShellContext'; -const runtimeApiBase = - typeof window !== 'undefined' && window.__PAPERCRATE_API_BASE_URL - ? window.__PAPERCRATE_API_BASE_URL - : ''; - -const DEFAULT_DEV_API = 'http://127.0.0.1:3000'; const ASSET_PRESIGN_TTL_MS = 240 * 1000; // backend issues 5 min tokens; refresh slightly early const TAG_MIME_TYPES = ['application/x-papercrate-tag', 'text/papercrate-tag']; -const API_ROOT = (runtimeApiBase || process.env.API_BASE_URL || DEFAULT_DEV_API).replace(/\/$/, ''); - const api = axios.create({ - baseURL: API_ROOT ? `${API_ROOT}/api` : '/api', + baseURL: '/api', withCredentials: true, }); -const STORED_TOKEN = window.localStorage.getItem('papercrate_token') || ''; +const storage = typeof window !== 'undefined' ? window.sessionStorage : undefined; +const STORED_TOKEN = storage?.getItem('papercrate_token') || ''; let STORED_TENANT = null; -try { - const rawTenant = window.localStorage.getItem('papercrate_tenant'); - if (rawTenant) { - STORED_TENANT = JSON.parse(rawTenant); - } -} catch ( - // eslint-disable-next-line no-empty - error -) {} +if (storage) { + try { + const rawTenant = storage.getItem('papercrate_tenant'); + if (rawTenant) { + STORED_TENANT = JSON.parse(rawTenant); + } + } catch ( + // eslint-disable-next-line no-empty + error + ) {} +} if (STORED_TOKEN) { api.defaults.headers.common.Authorization = `Bearer ${STORED_TOKEN}`; } @@ -191,22 +186,22 @@ const AppStateProvider = ({ children }) => { const token = state.token || ''; if (token) { api.defaults.headers.common.Authorization = `Bearer ${token}`; - window.localStorage.setItem('papercrate_token', token); + storage?.setItem('papercrate_token', token); } else { delete api.defaults.headers.common.Authorization; - window.localStorage.removeItem('papercrate_token'); + storage?.removeItem('papercrate_token'); } }, [state.token]); useEffect(() => { if (state.tenant) { try { - window.localStorage.setItem('papercrate_tenant', JSON.stringify(state.tenant)); + storage?.setItem('papercrate_tenant', JSON.stringify(state.tenant)); } catch (error) { console.warn('Failed to persist tenant info', error); } } else { - window.localStorage.removeItem('papercrate_tenant'); + storage?.removeItem('papercrate_tenant'); } }, [state.tenant]); @@ -270,7 +265,7 @@ const ROW_KEY_SEPARATOR = ':'; const DOCUMENT_ROW_PREFIX = 'document'; const FOLDER_ROW_PREFIX = 'folder'; -const resolveApiPath = (path = '') => (API_ROOT ? `${API_ROOT}${path}` : path); +const resolveApiPath = (path = '') => path; const makeRowKey = (type, id) => id ? `${type}${ROW_KEY_SEPARATOR}${id}` : `${type}${ROW_KEY_SEPARATOR}`; @@ -571,6 +566,16 @@ const AppLayout = () => { }); }, []); + const initialRefreshAttemptedRef = useRef(Boolean(token)); + + useEffect(() => { + if (!token && !initialRefreshAttemptedRef.current && appStatus === 'logged-out') { + initialRefreshAttemptedRef.current = true; + console.log('[Auth] Attempting refresh at startup'); + refreshAccessToken().catch(() => {}); + } + }, [token, appStatus, refreshAccessToken]); + const clearFilters = useCallback(() => { setSearchQuery(''); setActiveTagFilters([]); diff --git a/frontend/webpack.config.js b/frontend/webpack.config.js index 78c96bb..9a2feb8 100644 --- a/frontend/webpack.config.js +++ b/frontend/webpack.config.js @@ -1,14 +1,5 @@ const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); -const webpack = require('webpack'); -const dotenv = require('dotenv'); - -const env = dotenv.config({ path: path.resolve(__dirname, '.env.local') }).parsed || {}; - -const DEFAULT_DEV_API = 'http://127.0.0.1:3000'; - -const API_BASE_URL = env.API_BASE_URL || process.env.API_BASE_URL || DEFAULT_DEV_API; - module.exports = { entry: './src/index.jsx', output: { @@ -46,9 +37,6 @@ module.exports = { template: path.resolve(__dirname, 'src/index.html'), favicon: false, }), - new webpack.DefinePlugin({ - 'process.env.API_BASE_URL': JSON.stringify(API_BASE_URL), - }), ], devServer: { static: { @@ -58,6 +46,14 @@ module.exports = { port: 5173, historyApiFallback: true, open: true, + proxy: [ + { + context: ['/api'], + target: 'http://127.0.0.1:3000', + changeOrigin: true, + secure: false, + }, + ], }, devtool: 'source-map', resolve: { diff --git a/k8s/papercrate/templates/frontend-deployment.yaml b/k8s/papercrate/templates/frontend-deployment.yaml index 2a6c7a0..b85341b 100644 --- a/k8s/papercrate/templates/frontend-deployment.yaml +++ b/k8s/papercrate/templates/frontend-deployment.yaml @@ -36,6 +36,8 @@ spec: containerPort: {{ .Values.frontend.service.port }} protocol: TCP env: + - name: API_PROXY_PASS + value: {{ printf "http://%s-backend:%d" (include "papercrate.fullname" . ) (int (.Values.backend.service.port)) | quote }} {{- range .Values.frontend.env.extra }} - name: {{ .name }} value: {{ .value | quote }}