load skopec.dev live in its window via an allowlisted header-stripping proxy (/api/frame)

This commit is contained in:
Martin Dvořák 2026-09-30 06:42:36 +00:00
commit a4a12bfe2a
5 changed files with 147 additions and 12 deletions

View file

@ -84,6 +84,7 @@ src/app/
heatmap/route.ts → proxy for git.dvorakmortonxd.dev contribution heatmap
activities/route.ts → proxy for the user's GitDXD activity feed
ping/route.ts → service health check (HTTP + minecraft:// via mcstatus.io); host allowlist
frame/route.ts → header-stripping proxy so an unframeable friend site loads in a window; host allowlist
components/
ThemeRoot.tsx → picks which shell to show (the router of the UI)
TerminalLayout.tsx → "terminal" theme (Gruvbox)
@ -299,10 +300,16 @@ list reordering, not free placement. Don't confuse the two.
is the `embed` flag; `friend.url` is what the frame loads and `friend.realUrl`
is what the www button opens — `createWebsiteApp`'s `embedUrl` option lets the
two differ. `bukovinafilip.com` and `vlcak.me` send no framing headers, so they
frame the live site; `skopec.dev` sends `X-Frame-Options: SAMEORIGIN` on every
path, so `friends[0].url` points at the bundled `/public/skopec.html` mirror
and only the button is live. Making a blocking site load *live* would need a
header-stripping proxy (allowlisted) — not implemented.
frame the live site. `skopec.dev` sends `X-Frame-Options: SAMEORIGIN` on every
path, so `friend.proxy` routes it through `/api/frame`.
- `/api/frame` is a **host-allowlisted** header-stripping proxy for sites that
refuse framing. It fetches the HTML server-side (so the upstream
`X-Frame-Options` never reaches the browser), injects `<base href="origin/">`
so relative assets still resolve to the real site, and injects a click shim so
same-host navigation is proxied too. It only proxies `ALLOWED_HOSTS`
(`skopec.dev`), never follows redirects, rejects credentials and non-http(s),
and returns HTML only — it is public, so keep it that narrow. The framed page
executes under our origin, so only allow-list hosts you trust.
- **Embedded iframes swallow the pointer.** While dragging/resizing a window,
the moment the cursor passes over a service `<iframe>` the site's own document
receives the `pointermove`/`pointerup`, so a gesture that listens on `window`
@ -346,6 +353,9 @@ list reordering, not free placement. Don't confuse the two.
local dir and `LD_LIBRARY_PATH`. Frame the test from an `http(s)` origin (e.g.
a file dropped in `public/` served at `localhost:3000`); a `file://`
top-level blocks the iframe's storage and makes working sites look blank.
For a site that refuses framing, `/api/frame?url=<site>` should return `200`
with no `X-Frame-Options` and `frame-ancestors 'self'`, while `?url=` for any
other host returns `403`.
---