Files
SnapOtter/apps/landing/public/_worker.js
T
SnapOtterandGitHub d690a6e26d feat(landing): add a live system status indicator to the footer (#641)
Adds /api/status to the landing Pages worker, HEAD-probing demo.snapotter.com and
docs.snapotter.com with a 2 second per-attempt deadline and one retry. snapotter.com is
not probed; the worker answering the request is the proof it is up.

The footer badge ships grey in the static HTML and only upgrades once the route answers.
A rejected fetch, a non-ok response, an unparseable body, and an unrecognized verdict all
leave it grey, so it never claims green on its own.

Color lives in the dot, never the label: `--color-success` scores 4.498:1 against the
footer's `--color-background-alt`, just under AA.

Four labels across 21 locales.
2026-07-25 20:52:42 +08:00

82 lines
3.0 KiB
JavaScript

// snapotter.com needs no probe: this worker answering the request is the proof
// it is up. Only the two sibling properties are checked.
const STATUS_PROBES = ["https://demo.snapotter.com/", "https://docs.snapotter.com/"];
// Probed at the edge rather than read from the Sentry uptime API: no standing
// credential on a public worker, and no 15-minute lag from Sentry's 5-minute
// interval times its 3-strike rule.
//
// One immediate retry absorbs a transient blip. `redirect: "manual"` stops the
// follow, so a 3xx arrives as-is and still counts as up.
async function probe(url) {
for (let attempt = 0; attempt < 2; attempt++) {
// Built outside the try, so a throw here surfaces as the runtime bug it is
// rather than being read as a down leg. Built inside the loop, so each
// attempt carries its own deadline: hoisting it above the loop would leave
// attempt 2 holding an already-fired signal, silently deleting the retry
// for the exact transient-blip case the retry exists to absorb.
const signal = AbortSignal.timeout(2000);
let res;
try {
res = await fetch(url, { method: "HEAD", redirect: "manual", signal });
} catch {
// A throw is a timeout or a network error, which is a legitimate "down"
// signal rather than a swallowed bug. Retry once, then report the leg
// down. Only the fetch is guarded, so nothing below can be swallowed.
continue;
}
if (res.status < 400) return true;
}
return false;
}
async function statusResponse() {
const legs = await Promise.all(STATUS_PROBES.map((url) => probe(url)));
const downCount = legs.filter((up) => !up).length;
const status = downCount === 0 ? "operational" : downCount === legs.length ? "down" : "partial";
// A false green is cheap to sit on for a minute; a false red is not, and it
// would otherwise pin in the browser across every navigation until it aged
// out. Recheck a bad verdict sooner.
const maxAge = status === "operational" ? 60 : 15;
// `_headers` only decorates env.ASSETS responses in advanced mode, so a
// synthesized response has to carry its own.
return new Response(JSON.stringify({ status }), {
headers: {
"Content-Type": "application/json; charset=utf-8",
"Cache-Control": `public, max-age=${maxAge}`,
"X-Robots-Tag": "noindex",
},
});
}
export default {
async fetch(request, env) {
const url = new URL(request.url);
if (url.hostname === "www.snapotter.com") {
url.hostname = "snapotter.com";
return Response.redirect(url.toString(), 301);
}
if (url.pathname === "/api/status") {
return statusResponse();
}
const response = await env.ASSETS.fetch(request);
if (url.pathname.startsWith("/_next/static/") || url.pathname.startsWith("/_next/data/")) {
const headers = new Headers(response.headers);
headers.set("X-Robots-Tag", "noindex, nofollow");
return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers,
});
}
return response;
},
};