Skip to content

Commit e60e371

Browse files
Implement visit counter Cloudflare Worker with CORS
This Cloudflare Worker script manages a visit counter using KV storage. It supports CORS for a specific origin and handles GET and POST requests to read and increment the visit count.
1 parent 101a3a2 commit e60e371

1 file changed

Lines changed: 71 additions & 0 deletions

File tree

worker/src/index.js

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/**
2+
* realmathmodel visit counter — Cloudflare Worker + KV
3+
*
4+
* GET / -> read the current total, no increment
5+
* POST / -> increment the total, return the new value
6+
* OPTIONS -> CORS preflight
7+
*
8+
* Response body is always {"count": <number>}.
9+
* CORS is locked to the site origin; no secrets live in the page.
10+
*/
11+
12+
const ALLOWED_ORIGIN = "https://realmathmodel.github.io";
13+
const KEY = "total";
14+
15+
function corsHeaders(origin) {
16+
const h = {
17+
"Vary": "Origin",
18+
"Cache-Control": "no-store, no-cache, must-revalidate, max-age=0",
19+
};
20+
if (origin === ALLOWED_ORIGIN) {
21+
h["Access-Control-Allow-Origin"] = ALLOWED_ORIGIN;
22+
h["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS";
23+
h["Access-Control-Allow-Headers"] = "Content-Type";
24+
h["Access-Control-Max-Age"] = "86400";
25+
}
26+
return h;
27+
}
28+
29+
function json(body, status, origin) {
30+
return new Response(JSON.stringify(body), {
31+
status,
32+
headers: Object.assign(
33+
{ "Content-Type": "application/json; charset=utf-8" },
34+
corsHeaders(origin)
35+
),
36+
});
37+
}
38+
39+
export default {
40+
async fetch(request, env) {
41+
const origin = request.headers.get("Origin");
42+
const cors = corsHeaders(origin);
43+
44+
if (request.method === "OPTIONS") {
45+
// Preflight. Only the allowed origin gets ACAO headers back, so any
46+
// other origin's preflight fails in the browser as intended.
47+
return new Response(null, { status: 204, headers: cors });
48+
}
49+
50+
if (request.method !== "GET" && request.method !== "POST") {
51+
return json({ error: "method not allowed" }, 405, origin);
52+
}
53+
54+
// Reject cross-origin reads from anywhere else outright. A missing Origin
55+
// header (curl, server-side) is allowed to read but not to increment.
56+
if (origin && origin !== ALLOWED_ORIGIN) {
57+
return json({ error: "forbidden" }, 403, origin);
58+
}
59+
60+
const raw = await env.HITS.get(KEY);
61+
let count = parseInt(raw || "0", 10);
62+
if (!Number.isFinite(count) || count < 0) count = 0;
63+
64+
if (request.method === "POST" && origin === ALLOWED_ORIGIN) {
65+
count = count + 1;
66+
await env.HITS.put(KEY, String(count));
67+
}
68+
69+
return json({ count }, 200, origin);
70+
},
71+
};

0 commit comments

Comments
 (0)