SPB Git
1.4 KB · 15 lines typescript
1import "server-only";2import { createHash } from "node:crypto";3import { sqlPublic as sql } from "@/db";4const BOT = /bot|crawl|spider|slurp|facebookexternalhit|preview|curl\/|wget|python-requests|httpx|go-http-client|headless|lighthouse|pingdom|uptime|monitor|scrapy|semrush|ahrefs|mj12|dotbot|petalbot|bytespider|gptbot|claudebot|ccbot|anthropic|openai/i;5export function isBot(ua: string | null) { return !ua || BOT.test(ua); }6/** Visitor hash: sha256(ip, user-agent, daily salt) — not reversible, rotates daily, one event per visitor per paper per day. */7export function visitorHash(ip: string, ua: string | null) {8  const salt = process.env.SESSION_SECRET ?? "salt"; const day = new Date().toISOString().slice(0, 10);9  return createHash("sha256").update(`${salt}|${day}|${ip}|${ua ?? ""}`).digest("hex").slice(0, 32);10}11export async function recordEvent(kind: "view" | "download", paperId: string, h: { ip: string; ua: string | null; referrer?: string | null; locale?: string; versionId?: string | null }) {12  let host: string | null = null; try { host = h.referrer ? new URL(h.referrer).hostname : null; } catch { host = null; }13  await sql`INSERT INTO events (paper_id, version_id, kind, visitor_hash, referrer_host, locale, is_bot) VALUES (${paperId}, ${h.versionId ?? null}, ${kind}, ${visitorHash(h.ip, h.ua)}, ${host}, ${h.locale ?? null}, ${isBot(h.ua)}) ON CONFLICT DO NOTHING`;14}15
Files · main
Menu

Theme