serve api on same origin at /api

This commit is contained in:
2025-10-29 01:52:48 +01:00
parent 3bdca88144
commit 0052dff119
8 changed files with 71 additions and 56 deletions
+1 -2
View File
@@ -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
+31 -5
View File
@@ -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 <<CONFIG > /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 <<PROXY >> /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 "$@"
-11
View File
@@ -1,11 +0,0 @@
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri /index.html;
}
}
-1
View File
@@ -1 +0,0 @@
window.__PAPERCRATE_API_BASE_URL = window.__PAPERCRATE_API_BASE_URL || '';
-1
View File
@@ -6,7 +6,6 @@
<title>Papercrate</title>
</head>
<body>
<script src="/config.js"></script>
<main id="app"></main>
</body>
</html>
+29 -24
View File
@@ -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([]);
+8 -12
View File
@@ -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: {