diff --git a/README.md b/README.md index 3e8c11c..ef0180f 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Our objective is to provide a comprehensive, user-friendly weather platform for - **Framework**: [SvelteKit](https://kit.svelte.dev/) - **Language**: [TypeScript](https://www.typescriptlang.org/) - **Data Source**: [Open-Meteo API](https://open-meteo.com/) -- **Visualization**: [Apache Echarts](https://echarts.apache.org) +- **Visualization**: custom canvas charts (`src/lib/charts`) ## Developing @@ -33,3 +33,61 @@ npm run build ``` You can preview the production build with `npm run preview`. + +## Deployment (static hosting) + +The build output in `build/` is a fully static site. Two pieces of server +configuration are needed: + +### 1. SPA fallback + +Pages that are not prerendered (unlisted cities, GPS coordinate routes like +`/weather/week/52.09N5.12E/`) are served by `404.html`, which boots the app +and resolves the location client-side. Configure the server to serve +`404.html` for unknown paths. + +### 2. Cross-origin isolation (SharedArrayBuffer for the embedded map) + +The `/weather/maps/` page embeds `maps.open-meteo.com`, which uses +`SharedArrayBuffer` for its decoding worker pool. A cross-origin iframe only +gets `SharedArrayBuffer` when the **embedding** page is cross-origin +isolated, so this site must be served with: + +``` +Cross-Origin-Opener-Policy: same-origin +Cross-Origin-Embedder-Policy: require-corp +``` + +(The map already serves `Cross-Origin-Resource-Policy: cross-origin` and its +own COOP/COEP, so it is embeddable under these headers. All other assets are +same-origin and the weather APIs are CORS requests, so `require-corp` is safe +here.) + +### Example: Caddy + +```caddy +ombrella.example.com { + root * /srv/ombrella + file_server + try_files {path} {path}/ /404.html + header { + Cross-Origin-Opener-Policy "same-origin" + Cross-Origin-Embedder-Policy "require-corp" + } +} +``` + +### Example: nginx + +```nginx +server { + server_name ombrella.example.com; + root /srv/ombrella; + error_page 404 /404.html; + add_header Cross-Origin-Opener-Policy "same-origin" always; + add_header Cross-Origin-Embedder-Policy "require-corp" always; + location / { + try_files $uri $uri/ =404; + } +} +``` diff --git a/src/lib/components/navigation/weather-nav.svelte b/src/lib/components/navigation/weather-nav.svelte index b9ec104..392f62b 100644 --- a/src/lib/components/navigation/weather-nav.svelte +++ b/src/lib/components/navigation/weather-nav.svelte @@ -135,7 +135,7 @@
+ import { onMount } from 'svelte'; + import { storedLocation } from '$lib/stores/settings'; + // a #zoom/lat/lng(/bearing/pitch) hash on OUR url is piped through to the + // map, so map positions can be bookmarked/shared via ombrella links + // (the reverse direction is not possible: the iframe is cross-origin, so + // its internal hash changes are unreadable from here) + let hashOverride = $state(null); + + onMount(() => { + const readHash = () => { + const hash = window.location.hash; + hashOverride = /^#\d+(\.\d+)?\/-?\d+(\.\d+)?\/-?\d+(\.\d+)?/.test(hash) ? hash : null; + }; + readHash(); + window.addEventListener('hashchange', readHash); + return () => window.removeEventListener('hashchange', readHash); + }); + // the embedded map understands maplibre's #zoom/lat/lng hash, so the iframe // opens focused on the selected location (zoomed out to regional scale); // picking a new location while on this page recenters the map const iframeSrc = $derived( - `https://maps.open-meteo.com/#6/${$storedLocation.latitude.toFixed(3)}/${$storedLocation.longitude.toFixed(3)}` + `https://maps.open-meteo.com/${ + hashOverride ?? + `#6/${$storedLocation.latitude.toFixed(3)}/${$storedLocation.longitude.toFixed(3)}` + }` ); @@ -18,11 +39,15 @@
+