| Host | Role | Certificate |
|---|---|---|
astroblast.andrewnorman.org | Canonical. Route 53 A and AAAA alias records to the distribution. | ACM, DNS-validated, auto-renewing |
<id>.cloudfront.net | Always reachable. Test against this when DNS is in doubt. | CloudFront default |
TLS terminates at CloudFront using SNI, with a minimum protocol version of
TLSv1.2_2021 - CloudFront's strongest policy, which excludes every SHA-1 and
CBC cipher suite. Plain HTTP is redirected, not served.
All routes are GET only, served by CloudFront from an S3 origin. Any other
method returns 405 from the origin path; unmatched paths are rewritten to the
game page so a deep link never dead-ends.
| Route | Object | Type | Cache | Description |
|---|---|---|---|---|
GET / | index.html | text/html | no-cache, must-revalidate | The game. Default root object. |
GET /about.html | about.html | text/html | no-cache, must-revalidate | Project description, dependencies, privacy. |
GET /api.html | api.html | text/html | no-cache, must-revalidate | This page. |
GET /sitemap.html | sitemap.html | text/html | no-cache, must-revalidate | Human-readable site structure. |
GET /sitemap.xml | sitemap.xml | application/xml | no-cache, must-revalidate | Machine-readable sitemap. |
GET /robots.txt | robots.txt | text/plain | no-cache, must-revalidate | Crawler directives. |
GET /<anything else> | index.html | text/html | - | 403/404 from S3 is rewritten to /index.html with status 200. |
HTML is served with a short revalidating cache so a redeploy is visible immediately; the CloudFront edge cache is additionally invalidated on every deploy.
Applied by a CloudFront response headers policy on every response:
Strict-Transport-Security: max-age=63072000; includeSubDomains
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline';
style-src 'self' 'unsafe-inline'; img-src 'self' data:;
connect-src 'none'; base-uri 'none'; form-action 'none';
object-src 'none'; frame-ancestors 'none'
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
'unsafe-inline' is required because the game is deliberately a single
self-contained document with an inline <script> and
<style>. Since connect-src is 'none' and no
external origin is reachable, the residual risk is confined to the document itself.
| Backend | Key | Type | Example | Lifetime |
|---|---|---|---|---|
window.localStorage | astroblast.highscore | integer as string | "24850" | Until site data cleared |
Reads and writes are wrapped in try/catch: in private-browsing modes or
with site data blocked, the game runs normally and the high score is session-only.
The page exposes a small read/write handle on window for smoke tests and
console debugging. It is not a stable interface and carries no compatibility guarantee.
window.AstroBlast = {
version: "1.0.0",
G: { state, score, wave, lives, high, ship, rocks[], bullets[], foes[], parts[] },
CFG: { dt, ship{}, bullet{}, rockSizes{}, wave{}, saucer{}, ... },
startGame: function () // resets state and begins a new run
}
Example - drive a headless smoke test:
// in a browser console or Playwright page.evaluate()
AstroBlast.startGame();
AstroBlast.G.state; // "playing"
AstroBlast.G.rocks.length; // 4 on wave 1
A global leaderboard would add an Amazon API Gateway HTTP API in front of an AWS Lambda
function writing to Amazon DynamoDB, fronted by the same distribution under a
/api/* behaviour. That would require relaxing connect-src to
'self' and adding server-side score validation and rate limiting, since any
client-submitted score is untrusted by construction. It is intentionally not built here.