feat(server,web): serve Workspace HTML previews from a separate origin (#46)
Serve "open in new tab" HTML previews from a separate origin (the loopback counterpart, or PENGUIN_PREVIEW_ORIGIN) with a signed, host-bound token, so localStorage/cookies/third-party embeds work while Agent-generated pages stay off the app origin. The app is canonicalized onto localhost and the preview host (127.0.0.1) serves only /preview/* — its /api answers 401 and app routes 302 to the canonical host — so the preview origin can neither set nor honor a session cookie.
This commit is contained in:
@@ -337,9 +337,21 @@ export const listWorkspaceFiles = (sessionId: string, path: string) =>
|
||||
export const workspaceFileUrl = (sessionId: string, path: string, download = false): string =>
|
||||
`/api/sessions/${sessionId}/files/content?path=${encodeURIComponent(path)}${download ? "&download=1" : ""}`;
|
||||
|
||||
/** Sandboxed top-level preview URL (open an html file in a new tab): real content type under a CSP sandbox, see the server route. */
|
||||
/**
|
||||
* "Open in a new tab" for a Workspace html file: an App-origin link that mints a signed
|
||||
* token and 302s to the separate preview origin, where the page gets a real origin with
|
||||
* working storage, cookies and third-party embeds.
|
||||
*
|
||||
* A link (not a fetch + `window.open`) on purpose — opening a tab after an await trips
|
||||
* popup blockers, and a script-opened window keeps an `opener` handle back to the App,
|
||||
* which is precisely the reference the separate origin exists to deny. Use it with
|
||||
* `rel="noopener noreferrer"`.
|
||||
*
|
||||
* Falls back server-side to the sandboxed same-origin preview when the deployment has no
|
||||
* usable preview origin; `previewIsolated` from /api/me says so in advance.
|
||||
*/
|
||||
export const workspaceFilePreviewUrl = (sessionId: string, path: string): string =>
|
||||
`/api/sessions/${sessionId}/files/content?path=${encodeURIComponent(path)}&preview=1`;
|
||||
`/api/sessions/${sessionId}/files/preview-redirect?path=${encodeURIComponent(path)}`;
|
||||
|
||||
export const uploadWorkspaceFile = (sessionId: string, path: string, dataBase64: string) =>
|
||||
apiFetch<void>(`/api/sessions/${sessionId}/files/content`, {
|
||||
|
||||
Reference in New Issue
Block a user