nix/hive-gateway: SPA fallback via Accept-header map, not extension allowlist (#686)
mara on PR #729: "this still feels hacky - is there a proper way to do this?" damocles: agreed, "Accept-header map is meaningfully better than the allowlist [...] one map definition that encodes browser semantics directly, vs ~20 extensions to keep synced with whatever fluffychat (and any future hyperhive-served SPA) decides to ship". The previous shape (#684 catch-all regex, then this PR v1's extension allowlist) leaned on heuristics to distinguish "missing asset → 404" from "unknown SPA route → fall back to index.html". Both shapes were fragile against a SPA shipping a new extension, and the allowlist became dead code the moment a route ended in `.html-ish-suffix`. The proper distinction lives at the HTTP layer: top-frame browser navigations send `Accept: text/html,...` (chrome/firefox/safari are consistent on this). Asset fetches from script tags / img / fetch() / XHR send asset-typed Accepts (`image/*`, `application/javascript`, `*/*`) without `text/html`. Mechanics: an `nginx http`-context `map` keyed on `$http_accept` emits either `/matrix/index.html` (navigation) or a sentinel nonexistent path (`/__matrix_spa_no_html_fallback`); the location's `try_files $uri $uri/ $matrix_spa_target =404;` does the right thing for both cases. No extension list, no regex narrowing, no `if` block, no named-location fallback. The `map` lives in `services.nginx.appendHttpConfig` (only added when the matrix GUI is on, otherwise no `map` directive at all). The location's `extraConfig` is now a single `try_files` line. Verified via `nix eval` on both the rendered `appendHttpConfig` and the location's `extraConfig`. Full closure build pending operator deploy. Closes #686.
This commit is contained in:
parent
264c984398
commit
68bac7986b
1 changed files with 50 additions and 25 deletions
|
|
@ -147,6 +147,35 @@ in
|
|||
enable = true;
|
||||
recommendedProxySettings = true;
|
||||
recommendedOptimisation = true;
|
||||
# SPA-fallback target keyed on the `Accept` request header
|
||||
# (#686, mara + damocles on PR #729). This decides whether a
|
||||
# `/matrix/...` miss falls through to `index.html` (route
|
||||
# navigation) or returns a clean 404 (asset miss) — see the
|
||||
# `/matrix/` location comment below for the full rationale.
|
||||
#
|
||||
# Top-frame browser navigations always send
|
||||
# `Accept: text/html,...` (chrome/firefox/safari are
|
||||
# consistent on this). Asset fetches from script tags / img
|
||||
# / fetch() / XHR send asset-typed Accepts (`image/*`,
|
||||
# `application/javascript`, `*/*`) without `text/html`.
|
||||
# Mapping is purely on the header → no extension allowlist
|
||||
# to keep in sync with whatever the SPA ships, no regex
|
||||
# heuristic to false-positive on dot-segment routes.
|
||||
#
|
||||
# `$matrix_spa_target` defaults to a sentinel nonexistent
|
||||
# path so `try_files` falls through to the trailing `=404`
|
||||
# for asset misses. Browser navigations route to
|
||||
# `/matrix/index.html` where the SPA's client-side router
|
||||
# takes over.
|
||||
#
|
||||
# Only emitted when the matrix GUI is on (saves a no-op
|
||||
# `map` directive otherwise).
|
||||
appendHttpConfig = lib.optionalString (matrixCfg.enable && matrixCfg.gui.enable) ''
|
||||
map $http_accept $matrix_spa_target {
|
||||
default "/__matrix_spa_no_html_fallback";
|
||||
"~*text/html" "/matrix/index.html";
|
||||
}
|
||||
'';
|
||||
virtualHosts."_" = {
|
||||
listen = [
|
||||
{
|
||||
|
|
@ -158,35 +187,31 @@ in
|
|||
# Matrix GUI: when the operator has flipped both
|
||||
# `services.hyperhive.matrix.enable` and `matrix.gui.enable`
|
||||
# on, nginx serves fluffychat-web (or whatever override)
|
||||
# as a static dist at `/matrix/`. fluffychat is a SPA
|
||||
# — fall back to its index.html on deep links.
|
||||
# as a static dist at `/matrix/`.
|
||||
#
|
||||
# SPA fallback (iris/#643, rewritten in #686 per mara
|
||||
# + damocles on PR #729): the original
|
||||
# `try_files $uri $uri/ /matrix/index.html;` shape
|
||||
# silently masked missing assets — flutter's bootstrap
|
||||
# requesting e.g. `/matrix/native_executor.js` got
|
||||
# `index.html` (Content-Type: text/html, status 200)
|
||||
# when the file was absent from the dist, so the JS
|
||||
# runtime never loaded and `/matrix/` rendered blank
|
||||
# without any visible error.
|
||||
#
|
||||
# The followup #729 narrowed it with an extension
|
||||
# allowlist; this version uses `$matrix_spa_target`
|
||||
# (defined in the `appendHttpConfig` above, keyed on
|
||||
# the `Accept` header) so the decision lives in HTTP
|
||||
# semantics rather than a maintained extension list.
|
||||
# Navigations (Accept: text/html) fall to index.html;
|
||||
# asset fetches (Accept: */*, image/*, etc.) get a
|
||||
# clean 404 via the trailing `=404`.
|
||||
lib.optionalAttrs (matrixCfg.enable && matrixCfg.gui.enable) {
|
||||
"/matrix/" = {
|
||||
alias = "${matrixCfg.gui.package}/";
|
||||
extraConfig = ''
|
||||
try_files $uri $uri/ @matrix_spa_fallback;
|
||||
'';
|
||||
};
|
||||
# SPA fallback (iris/#643). The naive
|
||||
# `try_files $uri $uri/ /matrix/index.html;` shape
|
||||
# silently masked missing static assets — flutter's
|
||||
# bootstrap requesting e.g. `/matrix/native_executor.js`
|
||||
# got `index.html` (Content-Type: text/html, status
|
||||
# 200) when the file was absent from the dist, so
|
||||
# the JS runtime never loaded and `/matrix/` rendered
|
||||
# blank without any visible error.
|
||||
#
|
||||
# Asset-shaped URIs (anything with a `.<ext>` segment)
|
||||
# get an explicit 404 so the SPA + browser see the
|
||||
# missing-asset error cleanly. Only route-shaped URIs
|
||||
# (no extension) fall through to index.html for SPA
|
||||
# client-side routing.
|
||||
"@matrix_spa_fallback" = {
|
||||
extraConfig = ''
|
||||
if ($uri ~ "\.[A-Za-z0-9]+$") {
|
||||
return 404;
|
||||
}
|
||||
rewrite ^ /matrix/index.html last;
|
||||
try_files $uri $uri/ $matrix_spa_target =404;
|
||||
'';
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue