perf(home): parallelize private visitor tracking
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
Reference in New Issue
Block a user