Skip to content

Commit 3554b6f

Browse files
committed
Setup for cloud flare
1 parent 5bb076d commit 3554b6f

File tree

9 files changed

+127
-26
lines changed

9 files changed

+127
-26
lines changed
File renamed without changes.
File renamed without changes.

ai/storage.js

Lines changed: 49 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
document.addEventListener("DOMContentLoaded", () => {
2-
const SERVER_URL = "https://vps.unityailab.online:3000";
3-
const USE_LOCAL_FALLBACK = true;
2+
/* ─── Cloudflare‑only setup (no VPS) ───────────────────────────── */
3+
const USE_LOCAL_FALLBACK = false; // set true only for offline dev
4+
/* visitor‑counter cache */
5+
const VISITOR_CACHE_MS = 5 * 60 * 1000; // 5 minutes
6+
const VISITOR_TS_KEY = "visitor_ts";
7+
const VISITOR_CNT_KEY = "visitor_cnt";
8+
/* ──────────────────────────────────────────────────────────────── */
49

510
const sessionListEl = document.getElementById("session-list");
611
let sessions = loadSessions();
@@ -255,6 +260,8 @@ document.addEventListener("DOMContentLoaded", () => {
255260
location.reload();
256261
}
257262

263+
/* ───── user‑ID registration (now via /api/registerUser) ───── */
264+
258265
function initUserChecks() {
259266
let firstLaunch = localStorage.getItem("firstLaunch");
260267
if (firstLaunch === null) {
@@ -303,7 +310,7 @@ document.addEventListener("DOMContentLoaded", () => {
303310
return true;
304311
}
305312
try {
306-
const response = await fetch(`${SERVER_URL}/api/registerUser`, {
313+
const response = await fetch("/api/registerUser", {
307314
method: "POST",
308315
headers: { "Content-Type": "application/json" },
309316
body: JSON.stringify({ userId })
@@ -323,35 +330,51 @@ document.addEventListener("DOMContentLoaded", () => {
323330
return Math.random().toString(36).substr(2, 9);
324331
}
325332

333+
/* ───── Cloudflare visitor‑counter ───── */
334+
326335
function startVisitorCountPolling() {
327336
const visitorCountDisplay = document.getElementById("visitor-count-display");
328337
if (!visitorCountDisplay) return;
329-
fetchVisitorCount().then((count) => {
330-
visitorCountDisplay.textContent = count.toString();
331-
}).catch((err) => {
332-
visitorCountDisplay.textContent = "Offline";
333-
console.warn("Failed to get visitor count:", err);
334-
});
338+
339+
async function update() {
340+
try {
341+
const count = await fetchVisitorCountCached();
342+
visitorCountDisplay.textContent = prettyNumber(count);
343+
} catch (err) {
344+
visitorCountDisplay.textContent = "Offline";
345+
console.warn("Failed to get visitor count:", err);
346+
}
347+
}
348+
349+
update();
350+
setInterval(update, 60_000); // refresh every minute
335351
}
336352

337-
async function fetchVisitorCount() {
338-
if (USE_LOCAL_FALLBACK) {
339-
return 1234;
353+
async function fetchVisitorCountCached() {
354+
const now = Date.now();
355+
const ts = +localStorage.getItem(VISITOR_TS_KEY) || 0;
356+
if (now - ts < VISITOR_CACHE_MS) {
357+
return +localStorage.getItem(VISITOR_CNT_KEY);
340358
}
341-
try {
342-
const response = await fetch(`${SERVER_URL}/api/visitorCount`);
343-
if (!response.ok) {
344-
throw new Error(`Server error: ${response.status}`);
345-
}
346-
const data = await response.json();
347-
if (data && typeof data.count === "number") {
348-
return data.count;
349-
} else {
350-
throw new Error("Invalid data format");
351-
}
352-
} catch (err) {
353-
console.error("Failed to fetch visitor count:", err);
354-
throw err;
359+
360+
if (USE_LOCAL_FALLBACK) {
361+
const stub = 1234;
362+
localStorage.setItem(VISITOR_TS_KEY, now);
363+
localStorage.setItem(VISITOR_CNT_KEY, stub);
364+
return stub;
355365
}
366+
367+
const { total } = await fetch("/api/visitors").then(r => r.json());
368+
localStorage.setItem(VISITOR_TS_KEY, now);
369+
localStorage.setItem(VISITOR_CNT_KEY, total);
370+
return total;
371+
}
372+
373+
function prettyNumber(n) {
374+
const abs = Math.abs(n);
375+
if (abs >= 1e9) return (n / 1e9).toFixed(abs >= 1e11 ? 0 : 2) + "B";
376+
if (abs >= 1e6) return (n / 1e6).toFixed(abs >= 1e8 ? 0 : 2) + "M";
377+
if (abs >= 1e3) return (n / 1e3).toFixed(abs >= 1e5 ? 0 : 2) + "K";
378+
return n.toString();
356379
}
357380
});

functions/api/registerUser.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/**
2+
* Cloudflare Pages Function
3+
* Route: /api/registerUser
4+
*
5+
* Accepts: POST { "userId": "abc123" }
6+
* Returns: { "status": "registered" | "exists" | "error" }
7+
*
8+
* – KV namespace binding: USERS –
9+
* Cloudflare Dashboard ▸ Pages ▸ Functions ▸ Settings ▸ “USERS”
10+
*/
11+
export async function onRequestPost({ request, env }) {
12+
try {
13+
const { userId } = await request.json();
14+
15+
if (!userId || typeof userId !== "string") {
16+
return new Response(
17+
JSON.stringify({ status: "error", msg: "invalid‑id" }),
18+
{ status: 400, headers: { "Content-Type": "application/json" } }
19+
);
20+
}
21+
22+
const already = await env.USERS.get(userId);
23+
if (already !== null) {
24+
return new Response(
25+
JSON.stringify({ status: "exists" }),
26+
{ headers: { "Content-Type": "application/json" } }
27+
);
28+
}
29+
30+
await env.USERS.put(userId, "1");
31+
return new Response(
32+
JSON.stringify({ status: "registered" }),
33+
{ headers: { "Content-Type": "application/json" } }
34+
);
35+
} catch (err) {
36+
return new Response(
37+
JSON.stringify({ status: "error", msg: err.message }),
38+
{ status: 500, headers: { "Content-Type": "application/json" } }
39+
);
40+
}
41+
}
42+

functions/api/visitors.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
* Cloudflare Pages Function
3+
* Route: /api/visitors
4+
*
5+
* – Requires a KV namespace bound as COUNTER –
6+
* Pages ▸ Functions ▸ Settings ▸ KV Bindings ▸ “COUNTER”
7+
*/
8+
export async function onRequestGet({ request, env }) {
9+
/* ---------- 1  check the cookie ---------- */
10+
const cookies = Object.fromEntries(
11+
(request.headers.get('Cookie') || '')
12+
.split(/;\s*/)
13+
.map(s => s.split('='))
14+
.filter(p => p.length === 2)
15+
);
16+
const known = cookies.vid;
17+
18+
/* ---------- 2  read / increment counter ---------- */
19+
let total = +(await env.COUNTER.get('total')) || 0;
20+
if (!known) {
21+
total = total + 1;
22+
await env.COUNTER.put('total', total.toString()); // eventual‑consistency write
23+
}
24+
25+
/* ---------- 3  edge‑cached JSON response ---------- */
26+
return new Response(JSON.stringify({ total }), {
27+
headers: {
28+
'Content-Type': 'application/json',
29+
'Set-Cookie': known
30+
? undefined
31+
: `vid=${crypto.randomUUID()}; Path=/; Max-Age=31536000; SameSite=Lax`,
32+
'Cache-Control': 'public, max-age=60'
33+
}
34+
});
35+
}
36+

0 commit comments

Comments
 (0)