@smolcap/ai-tracker-nextjs 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,39 @@
1
+ # @smolcap/ai-tracker-nextjs
2
+
3
+ Reports AI crawler visits to AI Tracker from a Next.js proxy. It never builds, awaits or
4
+ changes a response: your pages are served exactly as before, and every tracking error is swallowed.
5
+
6
+ ```sh
7
+ npm install @smolcap/ai-tracker-nextjs
8
+ ```
9
+
10
+ `proxy.ts` on Next.js 16+, `middleware.ts` on Next.js 13–15, next to `app/` or `pages/`:
11
+
12
+ ```ts
13
+ import { withAiTracker } from "@smolcap/ai-tracker-nextjs";
14
+
15
+ export default withAiTracker({
16
+ siteKey: process.env.AI_TRACKER_KEY,
17
+ endpoint: "https://your-ai-tracker-dashboard.example",
18
+ });
19
+
20
+ export const config = {
21
+ matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
22
+ };
23
+ ```
24
+
25
+ Already have a proxy? Pass it as the second argument. It receives the same arguments and its
26
+ return value (or thrown error) reaches Next.js untouched:
27
+
28
+ ```ts
29
+ export default withAiTracker({ siteKey: process.env.AI_TRACKER_KEY, endpoint }, (request) => {
30
+ // your existing logic
31
+ });
32
+ ```
33
+
34
+ - Only `GET`/`HEAD` page requests whose user agent looks like a crawler are reported, in the
35
+ background through `event.waitUntil`, with a 1.5 s timeout. Everything else returns immediately.
36
+ - A missing key or invalid endpoint turns tracking off with one warning; it never throws.
37
+ - The proxy runs before rendering, so status codes are not recorded. Visitor IPs are read only from
38
+ headers set by Vercel (`x-vercel-forwarded-for`) or Cloudflare (`cf-connecting-ip`).
39
+ - Remove it by deleting the file (or unwrapping your proxy) and redeploying.
@@ -0,0 +1,12 @@
1
+ export type AiTrackerConfig = {
2
+ /** The site key for this exact hostname. Tracking is off while it is empty. */
3
+ siteKey: string | undefined;
4
+ /** Your AI Tracker dashboard origin, e.g. https://tracker.example */
5
+ endpoint: string;
6
+ };
7
+ type FetchEvent = {
8
+ waitUntil(promise: Promise<unknown>): void;
9
+ };
10
+ export declare function withAiTracker(config: AiTrackerConfig): (request: Request, event: FetchEvent) => void;
11
+ export declare function withAiTracker<R extends Request, E extends FetchEvent, T>(config: AiTrackerConfig, middleware: (request: R, event: E) => T): (request: R, event: E) => T;
12
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,46 @@
1
+ // Wraps a Next.js proxy (middleware before Next.js 16). Your own function's return value, or nothing,
2
+ // goes back to Next.js untouched; this package never builds, awaits or changes a response.
3
+ // Classification and verification live on the tracker; this only forwards candidate visits.
4
+ const BOT_HINTS = /bot|crawler|spider|crawl|gpt|claude|perplexity|bing|applebot|bytespider|ccbot|amazon|amzn|meta-|duckassist|mistral|google|copilot|grok|kimi|qwen|cohere|msnbot/i;
5
+ const ASSET_PATH = /\.(?:js|mjs|cjs|css|map|png|jpe?g|gif|webp|avif|svg|ico|bmp|tiff?|woff2?|ttf|otf|eot|mp[34]|webm|ogg|wav|m4[av]|mov|wasm)$|^\/(?:api|_next|_vercel|cdn-cgi)(?:\/|$)/i;
6
+ function track(request, event, ingest, siteKey) {
7
+ const userAgent = request.headers.get("user-agent") ?? "";
8
+ if ((request.method !== "GET" && request.method !== "HEAD") || !BOT_HINTS.test(userAgent))
9
+ return;
10
+ const url = new URL(request.url);
11
+ if (ASSET_PATH.test(url.pathname) || url.hostname === ingest.hostname)
12
+ return;
13
+ // Only headers the platform sets itself; x-forwarded-for is visitor-supplied on many hosts.
14
+ const ip = request.headers.get("cf-connecting-ip") ?? request.headers.get("x-vercel-forwarded-for")?.split(",", 1)[0].trim();
15
+ // Sent after the response is on its way; a slow or failing tracker never reaches the visitor.
16
+ event.waitUntil(fetch(ingest, {
17
+ method: "POST",
18
+ headers: { Authorization: `Bearer ${siteKey}`, "Content-Type": "application/json" },
19
+ body: JSON.stringify({ href: `${url.origin}${url.pathname}`, ai: { userAgent, ...(ip ? { ip } : {}), source: "nextjs" } }),
20
+ // Manual redirects never forward the site key.
21
+ redirect: "manual",
22
+ signal: AbortSignal.timeout(1500),
23
+ }).then((response) => response.body?.cancel()).catch(() => { }));
24
+ }
25
+ export function withAiTracker(config, middleware) {
26
+ // A throw while this module loads would take the whole site down, so bad configuration only disables tracking.
27
+ let ingest;
28
+ try {
29
+ const url = new URL("/api/ingest", config.endpoint);
30
+ if (url.protocol === "https:" && !url.username && !url.password)
31
+ ingest = url;
32
+ }
33
+ catch { }
34
+ if (!ingest || !config.siteKey)
35
+ console.warn("[ai-tracker] Tracking is off: set siteKey and an https endpoint.");
36
+ return (request, event) => {
37
+ try {
38
+ if (ingest && config.siteKey)
39
+ track(request, event, ingest, config.siteKey);
40
+ }
41
+ catch {
42
+ // Tracking must never fail a request.
43
+ }
44
+ return middleware?.(request, event);
45
+ };
46
+ }
package/package.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "@smolcap/ai-tracker-nextjs",
3
+ "version": "1.0.0",
4
+ "description": "AI Tracker for Next.js: reports AI crawler visits from proxy.ts or middleware.ts without touching the response",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "sideEffects": false,
8
+ "exports": { ".": { "types": "./dist/index.d.ts", "default": "./dist/index.js" } },
9
+ "types": "./dist/index.d.ts",
10
+ "files": ["dist"],
11
+ "scripts": { "build": "tsc -p tsconfig.json", "prepublishOnly": "npm run build" },
12
+ "peerDependencies": { "next": ">=13.0.0" },
13
+ "publishConfig": { "access": "public" }
14
+ }