perf(home): parallelize private visitor tracking

This commit is contained in:
2026-08-09 21:35:37 +07:00 Unverified
parent 1dc44e0215
commit c0429a8e8e
5 changed files with 87 additions and 27 deletions
+8
View File
@@ -13,6 +13,14 @@ export function getCanonicalUrl() {
return requiredEnvironmentUrl("NEXTAUTH_URL");
}
export function getVisitorTrackingConfig() {
return {
secret: readFeatureEnvironment("Visitor tracking", [
"NEXTAUTH_SECRET",
] as const).NEXTAUTH_SECRET,
};
}
export function getDiscordServerConfig() {
const config = readFeatureEnvironment("Discord", [
"DISCORD_BOT_TOKEN",
+31
View File
@@ -0,0 +1,31 @@
import { describe, expect, test } from "bun:test";
import {
getClientAddress,
hashClientAddress,
} from "./visitor-identifiers";
describe("privacy-preserving visitor tracking", () => {
test("uses the first valid forwarded address", () => {
const headers = new Headers({
"x-forwarded-for": "203.0.113.9, 10.0.0.1",
"x-real-ip": "192.0.2.2",
});
expect(getClientAddress(headers)).toBe("203.0.113.9");
});
test("rejects untrusted non-IP identifiers", () => {
expect(
getClientAddress(new Headers({ "x-forwarded-for": "unknown" })),
).toBeNull();
});
test("creates a stable identifier without retaining the address", () => {
const address = "203.0.113.9";
const identifier = hashClientAddress(address, "test-secret");
expect(identifier).toBe(hashClientAddress(address, "test-secret"));
expect(identifier).not.toContain(address);
expect(identifier).toHaveLength(64);
});
});
+22
View File
@@ -0,0 +1,22 @@
import "server-only";
import { getVisitorTrackingConfig } from "@/lib/config/server";
import { getRedisClient } from "@/lib/redis";
import {
getClientAddress,
hashClientAddress,
} from "@/lib/visitor/visitor-identifiers";
const VISITOR_SET_KEY = "erika:unique_visitors:v2";
export async function trackUniqueVisitor(headers: Headers) {
const client = await getRedisClient();
const address = getClientAddress(headers);
if (address) {
const { secret } = getVisitorTrackingConfig();
await client.sadd(VISITOR_SET_KEY, hashClientAddress(address, secret));
}
return client.scard(VISITOR_SET_KEY);
}
+15
View File
@@ -0,0 +1,15 @@
import { createHmac } from "node:crypto";
import { isIP } from "node:net";
export function getClientAddress(headers: Headers) {
const forwardedAddress = headers
.get("x-forwarded-for")
?.split(",", 1)[0]
?.trim();
const address = forwardedAddress || headers.get("x-real-ip")?.trim();
return address && isIP(address) ? address : null;
}
export function hashClientAddress(address: string, secret: string) {
return createHmac("sha256", secret).update(address).digest("hex");
}