Skip to content

Commit c956344

Browse files
feat: enhance landing page with download tracking and new assets
Added a new redirect mechanism for download links on the landing page, allowing for tracking of download clicks by platform and country. Introduced a new SQL schema for storing download click data. Updated the landing page to use relative download links instead of GitHub URLs, improving user experience. Added a new .assetsignore file to manage asset publishing and updated .gitignore to include Wrangler local state files.
1 parent fc994e3 commit c956344

6 files changed

Lines changed: 153 additions & 12 deletions

File tree

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,6 @@ dist/
4747
# Desktop app
4848
node_modules/
4949
apps/desktop/dist/
50-
apps/desktop/src-tauri/gen/
50+
apps/desktop/src-tauri/gen/
51+
# Wrangler local state (Cloudflare Workers dev cache)
52+
.wrangler/

apps/landing/.assetsignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# `assets.directory` is this folder, so anything here is published unless it is
2+
# listed below. The site is the HTML, CSS and images; the Worker's own source
3+
# and configuration are not part of it.
4+
.assetsignore
5+
wrangler.jsonc
6+
schema.sql
7+
src
8+
.wrangler

apps/landing/index.html

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -74,18 +74,18 @@ <h1>All of Nepal, on your menu bar.</h1>
7474
</p>
7575

7676
<div class="download-cta" id="download">
77-
<a id="primary-download" class="btn-primary" href="https://github.com/adarshaacharya/sajilo/releases/latest/download/Sajilo-macos-arm64.dmg">
77+
<a id="primary-download" class="btn-primary" href="/dl/macos-arm64">
7878
Download for macOS
7979
</a>
8080
<p class="download-note">Free and open source · macOS, Windows, Linux</p>
8181
<details class="other-platforms" id="other-platforms">
8282
<summary>Other downloads</summary>
8383
<div class="other-platforms-links">
84-
<a href="https://github.com/adarshaacharya/sajilo/releases/latest/download/Sajilo-macos-arm64.dmg">macOS (Apple Silicon)</a>
85-
<a href="https://github.com/adarshaacharya/sajilo/releases/latest/download/Sajilo-macos-x64.dmg">macOS (Intel)</a>
86-
<a href="https://github.com/adarshaacharya/sajilo/releases/latest/download/Sajilo-windows-x64.exe">Windows</a>
87-
<a href="https://github.com/adarshaacharya/sajilo/releases/latest/download/Sajilo-linux-amd64.deb">Linux (.deb)</a>
88-
<a href="https://github.com/adarshaacharya/sajilo/releases/latest/download/Sajilo-linux-x86_64.AppImage">Linux (AppImage)</a>
84+
<a href="/dl/macos-arm64">macOS (Apple Silicon)</a>
85+
<a href="/dl/macos-x64">macOS (Intel)</a>
86+
<a href="/dl/windows">Windows</a>
87+
<a href="/dl/linux-deb">Linux (.deb)</a>
88+
<a href="/dl/linux-appimage">Linux (AppImage)</a>
8989
</div>
9090
</details>
9191
</div>
@@ -283,8 +283,8 @@ <h3>Linux</h3>
283283
var isWin = /Windows/.test(ua);
284284
var isLinux = /Linux/.test(ua) && !/Android/.test(ua);
285285

286-
var MAC_ARM = { href: "https://github.com/adarshaacharya/sajilo/releases/latest/download/Sajilo-macos-arm64.dmg", label: "Download for macOS (Apple Silicon)" };
287-
var MAC_INTEL = { href: "https://github.com/adarshaacharya/sajilo/releases/latest/download/Sajilo-macos-x64.dmg", label: "Download for macOS (Intel)" };
286+
var MAC_ARM = { href: "/dl/macos-arm64", label: "Download for macOS (Apple Silicon)" };
287+
var MAC_INTEL = { href: "/dl/macos-x64", label: "Download for macOS (Intel)" };
288288

289289
function setMac(target) {
290290
btn.href = target.href;
@@ -299,10 +299,10 @@ <h3>Linux</h3>
299299
}).catch(function () {});
300300
}
301301
} else if (isWin) {
302-
btn.href = "https://github.com/adarshaacharya/sajilo/releases/latest/download/Sajilo-windows-x64.exe";
302+
btn.href = "/dl/windows";
303303
btn.textContent = "Download for Windows";
304304
} else if (isLinux) {
305-
btn.href = "https://github.com/adarshaacharya/sajilo/releases/latest/download/Sajilo-linux-x86_64.AppImage";
305+
btn.href = "/dl/linux-appimage";
306306
btn.textContent = "Download for Linux (AppImage)";
307307
}
308308
})();

apps/landing/schema.sql

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
-- Download clicks, counted rather than logged.
2+
--
3+
-- One row per day, platform, country and referring site, holding a running
4+
-- total. Nothing identifies a visitor, and nothing here can be traced back to
5+
-- one — the table cannot answer "who", only "how many".
6+
--
7+
-- The referrer is reduced to a hostname on the way in. A full URL is a liability
8+
-- rather than an insight: it can carry the search terms someone typed, or the
9+
-- path of a private page that linked here. "reddit.com" is the whole of what is
10+
-- worth knowing.
11+
CREATE TABLE IF NOT EXISTS download_clicks (
12+
day TEXT NOT NULL, -- YYYY-MM-DD, UTC
13+
platform TEXT NOT NULL, -- macos-arm64, windows, linux-deb, …
14+
country TEXT NOT NULL, -- ISO country code, XX when unknown
15+
referrer TEXT NOT NULL, -- hostname only; "direct" when there is none
16+
clicks INTEGER NOT NULL DEFAULT 0,
17+
PRIMARY KEY (day, platform, country, referrer)
18+
);

apps/landing/src/index.js

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/**
2+
* The landing site, plus a counted redirect for every download link.
3+
*
4+
* Downloads are served by GitHub, not by us — the assets are large, GitHub is
5+
* more reliable than anything here, and its own per-asset counter is worth
6+
* keeping. But a link straight to `github.com` leaves no trace on this side, so
7+
* the site cannot tell whether a visit turned into a download. `/dl/<platform>`
8+
* is a doorway: it records that a click happened and then hands the visitor
9+
* straight to the same GitHub URL they would have got anyway.
10+
*
11+
* What is written down is a running count per day, platform and country. There
12+
* is no row per person: no address, no user agent, no identifier, nothing that
13+
* describes who downloaded, only how many did.
14+
*/
15+
16+
const OWNER_REPO = "adarshaacharya/sajilo";
17+
const RELEASES_LATEST = `https://github.com/${OWNER_REPO}/releases/latest`;
18+
19+
/** The asset each `/dl/<platform>` link stands for. */
20+
const ASSETS = {
21+
"macos-arm64": "Sajilo-macos-arm64.dmg",
22+
"macos-x64": "Sajilo-macos-x64.dmg",
23+
windows: "Sajilo-windows-x64.exe",
24+
"linux-deb": "Sajilo-linux-amd64.deb",
25+
"linux-appimage": "Sajilo-linux-x86_64.AppImage",
26+
};
27+
28+
function assetUrl(name) {
29+
return `https://github.com/${OWNER_REPO}/releases/latest/download/${name}`;
30+
}
31+
32+
export default {
33+
async fetch(request, env, ctx) {
34+
const url = new URL(request.url);
35+
const platform = url.pathname.startsWith("/dl/")
36+
? url.pathname.slice("/dl/".length)
37+
: null;
38+
39+
if (platform === null) {
40+
return env.ASSETS.fetch(request);
41+
}
42+
43+
const asset = ASSETS[platform];
44+
// An unknown platform still gets somewhere useful. A 404 here would mean a
45+
// stale link on a shared post silently costing a download.
46+
const target = asset ? assetUrl(asset) : RELEASES_LATEST;
47+
48+
if (asset) {
49+
// `waitUntil` keeps the redirect immediate, and keeps a database that is
50+
// down or over quota from ever standing between a visitor and the file.
51+
ctx.waitUntil(record(env, platform, request));
52+
}
53+
54+
return new Response(null, {
55+
status: 302,
56+
headers: {
57+
Location: target,
58+
// Each click is a click. A cached redirect would be counted once and
59+
// then never again for that visitor.
60+
"Cache-Control": "no-store",
61+
},
62+
});
63+
},
64+
};
65+
66+
async function record(env, platform, request) {
67+
if (!env.DB) return;
68+
69+
const day = new Date().toISOString().slice(0, 10);
70+
const country = request.cf?.country ?? "XX";
71+
const referrer = referrerHost(request.headers.get("Referer"));
72+
73+
try {
74+
await env.DB.prepare(
75+
`INSERT INTO download_clicks (day, platform, country, referrer, clicks)
76+
VALUES (?1, ?2, ?3, ?4, 1)
77+
ON CONFLICT (day, platform, country, referrer)
78+
DO UPDATE SET clicks = clicks + 1`,
79+
)
80+
.bind(day, platform, country, referrer)
81+
.run();
82+
} catch {
83+
// A missed tally is not worth a failed download.
84+
}
85+
}
86+
87+
/**
88+
* Which site sent them, and nothing more.
89+
*
90+
* A full referring URL can carry the search terms someone typed or the path of
91+
* a private page, neither of which this is entitled to keep. The hostname
92+
* answers the only question being asked — did that Reddit post work — so it is
93+
* all that is kept. Our own pages read as "direct": a visit that began on the
94+
* site is not a referral to it.
95+
*/
96+
function referrerHost(header) {
97+
if (!header) return "direct";
98+
try {
99+
const host = new URL(header).hostname.replace(/^www\./, "");
100+
return host === "sajilo.fyi" ? "direct" : host;
101+
} catch {
102+
return "direct";
103+
}
104+
}

apps/landing/wrangler.jsonc

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
11
{
22
"name": "sajilo",
3+
"main": "src/index.js",
34
"compatibility_date": "2026-08-23",
45
"assets": {
56
"directory": ".",
7+
"binding": "ASSETS",
68
"not_found_handling": "404-page"
7-
}
9+
},
10+
"d1_databases": [
11+
{
12+
"binding": "DB",
13+
"database_name": "sajilo-downloads",
14+
"database_id": "57dc714f-c9b0-4ea5-b14b-238bdc643e97"
15+
}
16+
]
817
}

0 commit comments

Comments
 (0)