Preview proxy tuneup
This commit is contained in:
@@ -192,29 +192,24 @@ devServerRouter.get('/logs', async (ctx) => {
|
||||
|
||||
// Proxy router — mounted outside protected router (no auth needed for sub-resources).
|
||||
// Security: only proxies to ports that were started via authenticated /start calls.
|
||||
devServerProxyRouter.all('/:slug/*', async (ctx) => {
|
||||
const slug = ctx.req.param('slug');
|
||||
if (!slug) return ctx.text('Not found', 404);
|
||||
|
||||
const entry = findEntryBySlug(slug);
|
||||
if (!entry) return ctx.text('No dev server running', 404);
|
||||
type ProxyParams = { entry: ServerEntry; slug: string; proxyPath: string; search: string; method: string; rawReq: Request };
|
||||
|
||||
async function proxyRequest({ entry, slug, proxyPath, search, method, rawReq }: ProxyParams): Promise<Response> {
|
||||
const prefix = `/api/dev-server-proxy/${slug}`;
|
||||
const targetUrl = `http://localhost:${entry.port}${proxyPath}${search}`;
|
||||
|
||||
touchEntry(entry);
|
||||
|
||||
const originalUrl = new URL(ctx.req.url);
|
||||
const prefix = `/api/dev-server-proxy/${slug}`;
|
||||
const proxyPath = originalUrl.pathname.replace(prefix, '') || '/';
|
||||
const targetUrl = `http://localhost:${entry.port}${proxyPath}${originalUrl.search}`;
|
||||
|
||||
try {
|
||||
const headers = new Headers(ctx.req.raw.headers);
|
||||
const headers = new Headers(rawReq.headers);
|
||||
headers.delete('host');
|
||||
headers.delete('authorization');
|
||||
|
||||
const res = await fetch(targetUrl, {
|
||||
method: ctx.req.method,
|
||||
method,
|
||||
headers,
|
||||
body: ctx.req.method !== 'GET' && ctx.req.method !== 'HEAD' ? ctx.req.raw.body : undefined,
|
||||
body: method !== 'GET' && method !== 'HEAD' ? rawReq.body : undefined,
|
||||
redirect: 'manual',
|
||||
});
|
||||
|
||||
@@ -235,13 +230,42 @@ devServerProxyRouter.all('/:slug/*', async (ctx) => {
|
||||
return new Response(rewritten, { status: res.status, headers: newHeaders });
|
||||
}
|
||||
|
||||
return new Response(res.body, {
|
||||
status: res.status,
|
||||
headers: res.headers,
|
||||
});
|
||||
return new Response(res.body, { status: res.status, headers: res.headers });
|
||||
} catch {
|
||||
return ctx.text('Dev server not reachable', 502);
|
||||
return new Response('Dev server not reachable', { status: 502 });
|
||||
}
|
||||
}
|
||||
|
||||
// Proxy handler: tries slug from URL first, falls back to Referer for misrouted
|
||||
// chunk requests caused by relative imports resolving at wrong path depth.
|
||||
devServerProxyRouter.all('/:slug/*', async (ctx) => {
|
||||
const slug = ctx.req.param('slug');
|
||||
if (!slug) return ctx.text('Not found', 404);
|
||||
|
||||
const originalUrl = new URL(ctx.req.url);
|
||||
const entry = findEntryBySlug(slug);
|
||||
|
||||
if (entry) {
|
||||
const prefix = `/api/dev-server-proxy/${slug}`;
|
||||
const proxyPath = originalUrl.pathname.replace(prefix, '') || '/';
|
||||
return proxyRequest({ entry, slug, proxyPath, search: originalUrl.search, method: ctx.req.method, rawReq: ctx.req.raw });
|
||||
}
|
||||
|
||||
// Slug didn't match a dev server — check Referer for the real slug
|
||||
const referer = ctx.req.header('referer');
|
||||
if (referer) {
|
||||
const match = referer.match(/\/api\/dev-server-proxy\/([^/]+)/);
|
||||
if (match) {
|
||||
const realSlug = match[1]!;
|
||||
const realEntry = findEntryBySlug(realSlug);
|
||||
if (realEntry) {
|
||||
const proxyPath = originalUrl.pathname.replace('/api/dev-server-proxy', '') || '/';
|
||||
return proxyRequest({ entry: realEntry, slug: realSlug, proxyPath, search: originalUrl.search, method: ctx.req.method, rawReq: ctx.req.raw });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ctx.text('No dev server running', 404);
|
||||
});
|
||||
|
||||
// Injected into proxied HTML to intercept fetch/XHR so absolute paths (e.g. /api/hello)
|
||||
|
||||
@@ -87,6 +87,24 @@ export const PreviewProvider = ({ children }: PreviewProviderProps) => {
|
||||
return () => { cancelled = true; };
|
||||
}, [slug]);
|
||||
|
||||
// Poll status while a server is supposedly running — auto-restart if it died (e.g. idle timeout)
|
||||
useEffect(() => {
|
||||
if (!slug || !url) return;
|
||||
|
||||
const interval = setInterval(async () => {
|
||||
try {
|
||||
const status = await client.get<DevServerStatus>(`/dev-server/status?slug=${encodeURIComponent(slug)}`);
|
||||
if (!status.running) {
|
||||
startServer(slug);
|
||||
}
|
||||
} catch {
|
||||
// ignore transient failures
|
||||
}
|
||||
}, 30_000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [slug, url]);
|
||||
|
||||
return (
|
||||
<PreviewContext
|
||||
value={{
|
||||
|
||||
Reference in New Issue
Block a user