fix: mixed content on bare-domain API calls + SSE stream error handling

Two fixes:
1. Bare-domain routing used url.protocol (always http: behind TLS
   termination) for redirects, causing mixed-content blocks. Added
   proto helper that uses https: on production domains. Also rewrite
   /{space}/api/... calls internally instead of redirecting to the
   subdomain.
2. rDesign SSE stream reader now catches QUIC protocol errors on
   stream close gracefully.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-03-24 12:20:20 -07:00
parent 50c003e8e3
commit 6e4d1436e0
1 changed files with 15 additions and 5 deletions

View File

@ -2769,6 +2769,8 @@ const server = Bun.serve<WSData>({
const host = req.headers.get("host"); const host = req.headers.get("host");
const hostClean = host?.split(":")[0] || ""; const hostClean = host?.split(":")[0] || "";
const subdomain = getSubdomain(host); const subdomain = getSubdomain(host);
// TLS is terminated by Cloudflare/Traefik — url.protocol is always http: internally
const proto = hostClean.includes("rspace.online") || hostClean.includes(".online") ? "https:" : url.protocol;
// ── Standalone domain → 301 redirect to rspace.online ── // ── Standalone domain → 301 redirect to rspace.online ──
// Check both bare domain and subdomain variants (e.g. rnotes.online, alice.rnotes.online) // Check both bare domain and subdomain variants (e.g. rnotes.online, alice.rnotes.online)
@ -3007,7 +3009,7 @@ const server = Bun.serve<WSData>({
console.error(`[Template] On-demand seed failed for "${subdomain}":`, e); console.error(`[Template] On-demand seed failed for "${subdomain}":`, e);
} }
const withoutTemplate = pathSegments.slice(0, -1).join("/"); const withoutTemplate = pathSegments.slice(0, -1).join("/");
return Response.redirect(`${url.protocol}//${url.host}/${withoutTemplate}`, 302); return Response.redirect(`${proto}//${url.host}/${withoutTemplate}`, 302);
} }
// Demo route: /{moduleId}/demo → rewrite to /demo/{moduleId} // Demo route: /{moduleId}/demo → rewrite to /demo/{moduleId}
@ -3103,16 +3105,24 @@ const server = Bun.serve<WSData>({
return app.fetch(rewrittenReq); return app.fetch(rewrittenReq);
} }
// rspace.online/{space}/{...} → redirect to {space}.rspace.online/{...} // rspace.online/{space}/{...} → handle space-prefixed paths
// (space is not a module ID — it's a space slug, canonicalize to subdomain) // (space is not a module ID — it's a space slug)
// Skip redirect for known server paths (api, admin, etc.) // Skip for known server paths (api, admin, etc.)
const serverPaths = new Set(["api", "admin", "admin-data", "admin-action", ".well-known"]); const serverPaths = new Set(["api", "admin", "admin-data", "admin-action", ".well-known"]);
if (!knownModuleIds.has(firstSegment) && !serverPaths.has(firstSegment) && pathSegments.length >= 2) { if (!knownModuleIds.has(firstSegment) && !serverPaths.has(firstSegment) && pathSegments.length >= 2) {
const secondSeg = pathSegments[1]?.toLowerCase();
const isApiCall = secondSeg === "api" || pathSegments.some((s, i) => i >= 1 && s === "api");
if (isApiCall) {
// API calls: rewrite internally (avoid redirect + mixed-content)
const rewrittenUrl = new URL(url.pathname + url.search, `http://localhost:${PORT}`);
return app.fetch(new Request(rewrittenUrl, req));
}
// Page navigation: redirect to canonical subdomain URL
const space = firstSegment; const space = firstSegment;
const rest = "/" + pathSegments.slice(1).join("/"); const rest = "/" + pathSegments.slice(1).join("/");
const baseDomain = hostClean.replace(/^www\./, ""); const baseDomain = hostClean.replace(/^www\./, "");
return Response.redirect( return Response.redirect(
`${url.protocol}//${space}.${baseDomain}${rest}${url.search}`, 301 `${proto}//${space}.${baseDomain}${rest}${url.search}`, 301
); );
} }
} }