# `KeenPhoenixSvelte.Apps.Proxy`
[🔗](https://github.com/KeenMate/keen-phoenix-svelte/blob/v1.0.0-rc.9/lib/keen_phoenix_svelte/apps/proxy.ex#L1)

Serves a registered app's bundle **same-origin** by fetching it upstream on
the server. This is the `:proxy` mode from `KeenPhoenixSvelte.Apps` — it turns a
cross-origin CDN bundle into a first-party asset so it isn't subject to CORS or
a strict CSP `script-src`, and lets you gate or cache it.

## Mounting

Forward the `:proxy_path` (defaults to `/apps`) to this plug in your router:

    forward "/apps", KeenPhoenixSvelte.Apps.Proxy

A request resolves via `KeenPhoenixSvelte.Apps.resolve/1`, serves the cached
bundle, and revalidates it upstream when it goes stale. Both shapes are handled:

  * `/apps/<name>` — a **single-file** app (`url:`), served as `text/javascript`.
  * `/apps/<name>/<sub-path>` — a **base-path** app (`base:`); the sub-path is
    appended to the upstream directory, so a whole JS + CSS + assets bundle
    proxies through one registration. Each file's `Content-Type` is derived from
    its extension (`.mjs`/`.js` → `text/javascript`, `.css` → `text/css`, else
    `MIME`), while JS is always forced to a module-friendly type.

The default prefix is the same `/apps` that local bundles load from. That's
intentional and safe: `Plug.Static` runs before the router, so local files at
`/apps/<name>/main.mjs` are served directly, and only unmatched paths
(`/apps/<name>`, the proxied bundles) fall through to this plug.

## Caching & freshness

The bytes are cached and revalidated by `KeenPhoenixSvelte.Apps.ProxyCache`
(ETS + single-flight conditional `GET`). Freshness is driven by
the upstream's own `Cache-Control` / `ETag` / `Last-Modified` when present, and
falls back to a `:ttl` (default 5 min) otherwise — so **unversioned** upstreams
(`cdn/app.js`) are re-checked on a cadence instead of being pinned forever. See
`KeenPhoenixSvelte.Apps` for the `:proxy_cache` config and per-app `ttl` /
`immutable` / `client_cache_control` overrides.

On the way out this plug forwards an `ETag` and a revalidate-friendly
`Cache-Control`, and answers the browser's own `If-None-Match` with a `304` —
completing a browser → Phoenix → origin conditional-request chain. The
`Cache-Control` value is resolved most-specific-first: a per-app
`client_cache_control:` string, else a per-app `immutable: true` (whose value is
the global `immutable_cache_control`, defaulting to a 1-year immutable string),
else the global `client_cache_control`, else the built-in default.

## Fetching

The upstream fetch uses Erlang's built-in `:httpc` by default. Override it for
tests or a different client with an `:app_provider` — either a 1-arity
`fn url -> {:ok, body_binary} end` (legacy; always treated as a fresh `200`) or
a 2-arity `fn url, validators -> {:ok, resp} | :not_modified | {:error, reason} end`
that can honor `validators.etag` / `validators.last_modified` for conditional
revalidation (`resp` is a map with `:body` and optional `:etag`,
`:last_modified`, `:cache_control`):

    config :keen_phoenix_svelte,
      app_provider: fn _url, _validators -> {:ok, %{body: "export default 1;"}} end

---

*Consult [api-reference.md](api-reference.md) for complete listing*
