1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/** @jsxImportSource npm:hono@3/jsx */
import { sqlite } from "https://esm.town/v/std/sqlite?v=6";
import { reloadOnSaveFetchMiddleware } from "https://esm.town/v/stevekrouse/reloadOnSave";
import { Hono } from "npm:hono";
function setupTable() {
return sqlite.execute(`create table if not exists counter_town (
time timestamp default current_timestamp,
user_agent text,
country text,
referrer text,
url text,
browser text,
device text
)`);
}
// await setupTable();
// when a val is served by saascustomdomains the url field is
// https://saascustomdomains.val.run/whatever/the/path/is
// TODO - we should probably not expose this to our users
// this rewrite removes that and stores the url underneath
function removeCustomDomainRewriter(req) {
const servedFor = req.headers.get("x-served-for");
return req.url.replace("saascustomdomains.val.run", servedFor);
}
function countRequest(req: Request) {
console.log(req);
return sqlite.execute({
sql: "insert into counter_town (user_agent, country, referrer, url, browser, device) values (?, ?, ?, ?, ?, ?)",
args: [
req.headers.get("user-agent"),
req.headers.get("cf-ipcountry"),
req.headers.get("referer"),
removeCustomDomainRewriter(req),
req.headers.get("sec-ch-ua"),
req.headers.get("sec-ch-ua-platform"),
],
});
}
export function counterTownMiddleware(handler) {
return async function(req: Request) {
// don't await this so it doesn't add to the timing
countRequest(req).catch(e => console.error(e));
return handler(req);
};
}
const app = new Hono();
export default reloadOnSaveFetchMiddleware(counterTownMiddleware(app.fetch));
app.get("/", async (c) => {
return c.html(
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Counter Town</title>
<link rel="icon" href="https://fav.farm/📈" />
<link rel="stylesheet" href="https://unpkg.com/tailwindcss@2.2.19/dist/tailwind.min.css" />
</head>
<body>
<div class="bg-indigo-50 min-h-screen text-indigo-900 font-sans">
<header class="p-4 flex justify-between items-center">
<div class="flex items-center">
<h1 class="text-2xl font-bold">Counter Town</h1>
<span class="ml-2 bg-indigo-200 text-indigo-800 px-2 py-1 rounded-full text-xs">Beta</span>
</div>
</header>
<main class="container mx-auto px-4 py-8">
<div class="text-center mb-12">
<h2 class="text-4xl font-bold mb-4">Medium-scale web analytics powered by Val Town</h2>
<p class="text-xl mb-8">
Simple and privacy-focused for personal blogs and community sites
</p>
<div>
<a
class="bg-indigo-600 text-white px-6 py-2 rounded-lg mr-4 hover:bg-indigo-700 transition"
href="https://www.val.town/v/stevekrouse/counterTown"
>
Get Started
</a>
<a
class="bg-indigo-100 text-indigo-700 px-6 py-2 rounded-lg hover:bg-indigo-200 transition"
href="/dashboard"
>
Browse the demo
</a>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-8 mb-12">
<div class="bg-white p-6 rounded-lg shadow">
<h3 class="text-xl font-semibold mb-2">Powered by Val Town</h3>
<p>
Counter.town runs entirely on Val Town's infrastructure, utilizing Val Town SQLite (Turso) for data
storage.
</p>
</div>