Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/llms-txt-hub / apps/web/app/api/cli/telemetry/route.ts |
| Date | Not specified in source |
| Repository | Janady13/llms-txt-hub |
| Revision | e0f4bf978d4df168369c7601d9c5f5ea89e84377 |
| Branch | main |
| Path or archive member | apps/web/app/api/cli/telemetry/route.ts |
| Original SHA-256 | 0b0e14873f924ea85fb89a2f8bf1e97f83de394c8f9d0658cd24e6c203f72a21 |
| Git blob SHA | 7c6b50271c39cd9279e5608ac42b2fa507fbf317 |
| Reading-copy SHA-256 | 0b0e14873f924ea85fb89a2f8bf1e97f83de394c8f9d0658cd24e6c203f72a21 |
Recovered source transcript
Download complete source text ↓140 lines · 3.8 KB
import { type NextRequest, NextResponse } from 'next/server'
import { getRawClient } from '@/lib/redis'
const ALLOWED_EVENTS = ['install', 'remove', 'init', 'update', 'search'] as const
const DAY_SECONDS = 86_400
const TTL_DAYS = 90
const TTL_SECONDS = TTL_DAYS * DAY_SECONDS
const RATE_LIMIT_MAX = 60
const RATE_LIMIT_WINDOW_SECONDS = 60
/**
* Extract client IP from request headers for rate limiting.
*/
function getRateLimitKey(request: NextRequest): string {
const forwarded = request.headers.get('x-forwarded-for')
const realIP = request.headers.get('x-real-ip')
const ip = forwarded ? forwarded.split(',')[0].trim() : realIP || 'unknown'
return ip
}
/**
* Check rate limit using Redis INCR + EXPIRE for serverless-safe windowed limiting.
* Returns null (allowed) or the number of seconds until the window resets.
*/
async function checkRateLimit(
clientIp: string
): Promise<{ allowed: boolean; retryAfterSeconds?: number }> {
const redis = getRawClient()
if (!redis) {
return { allowed: true }
}
const key = `telemetry:rate:${clientIp}`
const count = await redis.incr(key)
if (count === 1) {
await redis.expire(key, RATE_LIMIT_WINDOW_SECONDS)
}
if (count > RATE_LIMIT_MAX) {
const ttl = await redis.ttl(key)
return { allowed: false, retryAfterSeconds: ttl > 0 ? ttl : RATE_LIMIT_WINDOW_SECONDS }
}
return { allowed: true }
}
/**
* Return today's date in YYYY-MM-DD format for Redis key partitioning.
*/
function todayKey(): string {
return new Date().toISOString().slice(0, 10)
}
/**
* Type guard to validate that a value is a known telemetry event.
*/
function isValidEvent(value: unknown): value is (typeof ALLOWED_EVENTS)[number] {
if (typeof value !== 'string') return false
return ALLOWED_EVENTS.some(e => e === value)
}
/**
* Handle CLI telemetry POST requests.
*/
export async function POST(request: NextRequest) {
try {
const clientIp = getRateLimitKey(request)
const rateLimit = await checkRateLimit(clientIp)
if (!rateLimit.allowed) {
return NextResponse.json(
{ ok: false, error: 'Rate limit exceeded' },
{
status: 429,
headers: {
'Retry-After': (rateLimit.retryAfterSeconds ?? RATE_LIMIT_WINDOW_SECONDS).toString()
}
}
)
}
const body = await request.json()
const { event, skills, agents } = body
if (!isValidEvent(event)) {
return NextResponse.json({ ok: false, error: 'Invalid event type' }, { status: 400 })
}
const redis = getRawClient()
if (!redis) {
return NextResponse.json({ ok: true })
}
const date = todayKey()
const pipeline = redis.pipeline()
// Per-event type daily counter
pipeline.hincrby(`telemetry:events:${date}`, event, 1)
// Per-skill counters
const slugs =
skills && typeof skills === 'string'
? skills
.split(',')
.map(s => s.trim())
.filter(Boolean)
: []
for (const slug of slugs) {
pipeline.hincrby(`telemetry:daily:${date}`, slug, 1)
pipeline.hincrby('telemetry:skills:total', slug, 1)
}
// Per-agent-per-skill counters (e.g. telemetry:skills:agents:stripe → { cursor: 5, claude-code: 3 })
if (event === 'install' && slugs.length > 0 && agents && typeof agents === 'string') {
const agentNames = agents
.split(',')
.map(a => a.trim())
.filter(Boolean)
for (const slug of slugs) {
for (const agent of agentNames) {
pipeline.hincrby(`telemetry:skills:agents:${slug}`, agent, 1)
}
}
}
// Set TTL on daily keys
pipeline.expire(`telemetry:events:${date}`, TTL_SECONDS)
pipeline.expire(`telemetry:daily:${date}`, TTL_SECONDS)
await pipeline.exec()
return NextResponse.json({ ok: true })
} catch {
return NextResponse.json({ ok: true })
}
}