Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ jobs:
# de formatos entre proveedores y el analizador del back. Los que levantan
# salas o contenedores quedan fuera — necesitan Docker y varios minutos.
demos:
runs-on: ubuntu-latest
# Fijo y no ubuntu-latest: cuando latest pasó a 26.04, Playwright 1.56.1
# (el de docker/qa) ya no pudo instalar Chromium ahí y el job se cayó antes
# de la demo de ver_preview. Se sube cuando se suba Playwright.
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
Expand All @@ -57,6 +60,7 @@ jobs:
- run: npm run demo:ver-preview
- run: npm run demo:internet
- run: npm run demo:publicar
- run: npm run demo:proxy-rutas
- run: npm run demo:documento
- run: npm run demo:doc-sync
- run: npm run demo:comentarios
19 changes: 19 additions & 0 deletions server/src/demos/proxy-rutas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,25 @@ function main() {
check("un módulo sin cookie ni referer", aLaSala("/src/main.tsx", {}));
}

console.log("\n6b. Una app que navega a su inicio se queda en el preview");
{
/**
* El bug que cubre: un link "Inicio" (href="/") dentro de la app pedía la
* raíz desde el iframe, y se le servía la Sala entera: el chat de Multti
* aparecía dentro de la vista previa hasta que alguien recargaba.
*/
const desdeElIframe = { ...COOKIE, dest: "iframe" };
check("la raíz pedida desde el iframe va al preview", alPreview("/", desdeElIframe));
check("con query también", alPreview("/?pagina=2", desdeElIframe));
check("index.html desde el iframe también", alPreview("/index.html", desdeElIframe));
check("con el referer del preview basta, sin cookie", alPreview("/", { ...REFERER, dest: "iframe" }));
check("y conserva la ruta tal cual", parsePreviewUrl("/?pagina=2", desdeElIframe)?.rest === "/?pagina=2");
check("la raíz abierta como página sigue siendo la Sala", aLaSala("/", { ...COOKIE, dest: "document" }));
check("sin cabecera, sigue siendo la Sala", aLaSala("/"));
check("desde un iframe sin sala identificada, la Sala", aLaSala("/", { dest: "iframe" }));
check("lo de la Sala no cambia aunque venga de un iframe", aLaSala("/socket.io/?EIO=4", desdeElIframe));
}

console.log("\n7. La página de la sala se distingue de sus archivos");
{
/**
Expand Down
30 changes: 23 additions & 7 deletions server/src/engine/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ const ROOM_COOKIE = "multi_room";
*/
const ESPERAS = [250, 500, 1000, 2000];

/** Una cabecera que Node puede dar como lista, tomada como texto. */
function headerUnico(v: string | string[] | undefined): string | undefined {
return Array.isArray(v) ? v[0] : v;
}

/** Lee el roomId de la cookie (si viene). */
function roomFromCookie(cookieHeader?: string): string | null {
if (!cookieHeader) return null;
Expand All @@ -100,7 +105,7 @@ function roomFromCookie(cookieHeader?: string): string | null {
*/
export function parsePreviewUrl(
url: string,
headers: { referer?: string; cookie?: string },
headers: { referer?: string; cookie?: string; dest?: string },
): { roomId: string; rest: string } | null {
// Caso 1: URL explícita bajo /preview/:roomId/...
if (url.startsWith(PREFIX)) {
Expand All @@ -112,15 +117,25 @@ export function parsePreviewUrl(
return { roomId, rest };
}

// La raíz SIEMPRE es la Sala. La cookie del preview sobrevive en la pestaña,
// así que sin esto abrir Multi después de ver un preview te daría la app del
// proyecto en lugar de la Sala.
const soloRuta = url.split("?")[0];
if (soloRuta === "/") return null;
const fromReferer = headers.referer?.match(/\/preview\/([a-z0-9-]{1,64})(?:\/|$)/i)?.[1];

if (RUTAS_DE_LA_SALA.some((p) => soloRuta === p || soloRuta.startsWith(p))) return null;
// La raíz es la Sala, salvo que la pida el iframe del preview.
//
// La cookie del preview sobrevive en la pestaña, así que sin la primera regla
// abrir Multti después de ver un preview te daría la app del proyecto en vez
// de la Sala. Pero una app que navega a su inicio (un link "Inicio", el logo,
// un router que redirige a "/") pide la raíz DESDE el iframe, y ahí se le
// servía la Sala entera: el chat de Multti aparecía dentro de la vista previa
// hasta que alguien recargaba. El navegador dice quién pide con
// Sec-Fetch-Dest, y la Sala nunca se abre dentro de un iframe.
if (soloRuta === "/" || soloRuta === "/index.html") {
if (headers.dest !== "iframe") return null;
const roomId = fromReferer ?? roomFromCookie(headers.cookie);
return roomId ? { roomId, rest: url } : null;
}

const fromReferer = headers.referer?.match(/\/preview\/([a-z0-9-]{1,64})(?:\/|$)/i)?.[1];
if (RUTAS_DE_LA_SALA.some((p) => soloRuta === p || soloRuta.startsWith(p))) return null;

// `/assets/` lo usan los dos: solo va al preview si la petición viene de ahí.
if (soloRuta.startsWith(RUTA_COMPARTIDA) && !fromReferer) return null;
Expand Down Expand Up @@ -175,6 +190,7 @@ export function handlePreviewRequest(
const parsed = parsePreviewUrl(req.url ?? "", {
referer: req.headers.referer,
cookie: req.headers.cookie,
dest: headerUnico(req.headers["sec-fetch-dest"]),
});
if (D) console.log(`[proxy] IN url=${req.url} parsed=${JSON.stringify(parsed)}`);
if (!parsed) return false;
Expand Down
Loading