Skip to content

Commit e83c11f

Browse files
committed
Simplify preview URL core module
Move proxy metadata, forwarding request construction, and token helpers behind focused preview utilities so PreviewService stays centered on preview URL state and lifecycle decisions.
1 parent b826a12 commit e83c11f

7 files changed

Lines changed: 340 additions & 212 deletions

File tree

packages/sandbox/src/preview/protocol.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { validatePort } from '../security';
2+
13
/** @internal */
24
export const PREVIEW_PROXY_HEADER = 'x-sandbox-preview-proxy';
35
/** @internal */
@@ -14,3 +16,48 @@ export const PREVIEW_PROXY_HEADERS = [
1416
PREVIEW_PROXY_TOKEN_HEADER,
1517
PREVIEW_PROXY_SANDBOX_ID_HEADER
1618
] as const;
19+
20+
export interface PreviewProxyMetadata {
21+
port: number;
22+
token: string;
23+
sandboxId: string;
24+
}
25+
26+
export function isPreviewProxyRequest(request: Request): boolean {
27+
return request.headers.get(PREVIEW_PROXY_HEADER) === '1';
28+
}
29+
30+
export function readPreviewProxyMetadata(
31+
request: Request
32+
): PreviewProxyMetadata | null {
33+
if (!isPreviewProxyRequest(request)) {
34+
return null;
35+
}
36+
37+
const portValue = request.headers.get(PREVIEW_PROXY_PORT_HEADER);
38+
const token = request.headers.get(PREVIEW_PROXY_TOKEN_HEADER);
39+
const sandboxId = request.headers.get(PREVIEW_PROXY_SANDBOX_ID_HEADER);
40+
const port = portValue === null ? Number.NaN : Number.parseInt(portValue, 10);
41+
42+
if (!Number.isFinite(port) || !validatePort(port) || !token || !sandboxId) {
43+
return null;
44+
}
45+
46+
return { port, token, sandboxId };
47+
}
48+
49+
export function withPreviewProxyMetadata(
50+
request: Request,
51+
{ port, token, sandboxId }: PreviewProxyMetadata
52+
): Request {
53+
const headers = new Headers(request.headers);
54+
for (const header of PREVIEW_PROXY_HEADERS) {
55+
headers.delete(header);
56+
}
57+
headers.set(PREVIEW_PROXY_HEADER, '1');
58+
headers.set(PREVIEW_PROXY_PORT_HEADER, port.toString());
59+
headers.set(PREVIEW_PROXY_TOKEN_HEADER, token);
60+
headers.set(PREVIEW_PROXY_SANDBOX_ID_HEADER, sandboxId);
61+
62+
return new Request(request, { headers });
63+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { PREVIEW_PROXY_HEADERS } from './protocol';
2+
3+
export interface BuildPreviewProxyRequestOptions {
4+
port: number;
5+
sandboxId: string;
6+
sandboxName: string | null;
7+
}
8+
9+
export function buildPreviewProxyRequest(
10+
request: Request,
11+
{ port, sandboxId, sandboxName }: BuildPreviewProxyRequestOptions
12+
): Request {
13+
const url = new URL(request.url);
14+
const proxyURL = `http://localhost:${port}${url.pathname}${url.search}`;
15+
const headers = stripPreviewProxyHeaders(request.headers);
16+
17+
headers.set('X-Original-URL', request.url);
18+
headers.set('X-Forwarded-Host', url.hostname);
19+
headers.set('X-Forwarded-Proto', url.protocol.replace(':', ''));
20+
headers.set('X-Sandbox-Name', sandboxName ?? sandboxId);
21+
22+
const upgradeHeader = request.headers.get('Upgrade');
23+
if (upgradeHeader?.toLowerCase() === 'websocket') {
24+
return new Request(request, {
25+
headers,
26+
redirect: 'manual'
27+
});
28+
}
29+
30+
return new Request(proxyURL, {
31+
method: request.method,
32+
headers,
33+
body: request.body,
34+
// @ts-expect-error - duplex required for body streaming in modern runtimes
35+
duplex: 'half',
36+
redirect: 'manual'
37+
});
38+
}
39+
40+
function stripPreviewProxyHeaders(source: Headers): Headers {
41+
const headers = new Headers(source);
42+
for (const header of PREVIEW_PROXY_HEADERS) {
43+
headers.delete(header);
44+
}
45+
return headers;
46+
}

0 commit comments

Comments
 (0)