@apideck/agent-analytics 0.12.0 → 0.14.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/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- var C=/ClaudeBot|Claude-User|Claude-SearchBot|Claude-Web|Anthropic|GPTBot|ChatGPT-User|OAI-SearchBot|PerplexityBot|Perplexity-User|Google-Extended|Google-CloudVertexBot|Google-Agent|GoogleAgent-Mariner|Gemini-Deep-Research|Applebot|cohere|Bytespider|CCBot|Amazonbot|Amzn-SearchBot|NovaAct|AzureAI-SearchBot|Meta-ExternalAgent|meta-externalfetcher|meta-webindexer|FacebookBot|DuckAssistBot|MistralAI-User|YouBot|AI2Bot|Diffbot|DeepSeek|PanguBot|Webzio-Extended|omgili|Timpibot|Grok|Manus-User|quillbot|MyCentralAIScraperBot|Cursor|Windsurf/i,T=/axios\/|curl\/|(?:^|[\s(])got(?:\/|[\s(])|\bcolly\b|Electron\/|node-fetch\/|python-requests\/|Go-http-client\/|okhttp\/|aiohttp\/|Deno\//i;function d(t){return t?C.test(t):false}function g(t){return t?T.test(t):false}function S(t){if(!t||typeof t!="string")return "Other";let e=t.toLowerCase();return e.includes("chatgpt-user")||e.includes("gptbot")||e.includes("oai-searchbot")||e.includes("openai")?"ChatGPT":e.includes("claudebot")||e.includes("claude-user")||e.includes("claude-searchbot")||e.includes("claude-web")||e.includes("anthropic")?"Claude":e.includes("perplexitybot")||e.includes("perplexity-user")?"Perplexity":e.includes("ccbot")?"Common Crawl":e.includes("google-extended")||e.includes("googlebot")||e.includes("google-cloudvertexbot")||e.includes("google-agent")||e.includes("googleagent-mariner")||e.includes("gemini-deep-research")?"Google":e.includes("applebot")?"Apple":e.includes("bingbot")?"Bing":e.includes("bytespider")?"Bytespider":e.includes("amazonbot")||e.includes("amzn-searchbot")||e.includes("novaact")?"Amazon":e.includes("meta-externalagent")||e.includes("meta-externalfetcher")||e.includes("meta-webindexer")||e.includes("facebookbot")?"Meta":e.includes("mistralai-user")?"Mistral":e.includes("duckassistbot")?"DuckDuckGo":e.includes("youbot")?"You.com":e.includes("diffbot")?"Diffbot":e.includes("ai2bot")?"AI2":e.includes("cohere")?"Cohere":e.includes("cursor")?"Cursor":e.includes("windsurf")?"Windsurf":e.includes("deepseek")?"DeepSeek":e.includes("pangubot")?"Huawei":e.includes("webzio")||e.includes("omgili")?"Webz.io":e.includes("timpibot")?"Timpi":e.includes("grok")||e.includes("xai-")?"xAI":e.includes("manus-user")?"Manus":e.includes("quillbot")?"QuillBot":e.includes("azureai-searchbot")?"Microsoft":e.includes("mycentralaiscraperbot")?"MyCentralAI":e.includes("petalbot")?"PetalBot":e.includes("ahrefsbot")?"Ahrefs":e.includes("semrushbot")?"Semrush":e.includes("mj12bot")?"Majestic":e.includes("dotbot")||e.includes("rogerbot")?"Moz":e.includes("screaming frog")?"Screaming Frog":e.includes("sitebulb")?"Sitebulb":e.includes("linkfluence")?"Linkfluence":e.includes("dataforseo")?"DataForSEO":e.includes("serpstatbot")?"Serpstat":e.includes("uptimerobot")?"UptimeRobot":e.includes("pingdom")?"Pingdom":e.includes("statuscake")?"StatusCake":e.includes("newrelicpinger")?"New Relic":e.includes("datadogagent")||e.includes("datadog")?"Datadog":e.includes("slackbot")?"Slack":e.includes("twitterbot")?"Twitter":e.includes("linkedinbot")?"LinkedIn":e.includes("discordbot")?"Discord":e.includes("telegrambot")?"Telegram":e.includes("whatsapp")?"WhatsApp":e.includes("linkupbot")?"Linkup":e.includes("sogou")?"Sogou":e.includes("yandexbot")?"Yandex":e.includes("baiduspider")?"Baidu":e.includes("facebookexternalhit")?"Facebook":e.includes("com.apple.webkit")?"Apple URL Preview":e.includes("ohdear")?"Oh Dear":e.includes("scrapy")?"Scrapy":e.includes("headlesschrome")?"Headless Chrome":e.includes("phantomjs")?"PhantomJS":e.includes("wget")?"wget":e.includes("httpie")?"HTTPie":e.includes("guzzlehttp")?"Guzzle":e.includes("electron/")?"Electron":/curl\//.test(e)?"curl":/axios\//.test(e)?"axios":/(?:^|[\s(])got(?:\/|[\s(])/.test(e)?"got":/\bcolly\b/.test(e)?"colly":/node-fetch\//.test(e)?"node-fetch":/python-requests\//.test(e)?"python-requests":/go-http-client\//.test(e)?"Go http client":/okhttp\//.test(e)?"OkHttp":/aiohttp\//.test(e)?"aiohttp":/deno\//.test(e)?"Deno":e.includes("mozilla")||e.includes("chrome")||e.includes("safari")||e.includes("firefox")?"Browser":"Other"}function M(t){if(!t||typeof t!="string")return "Other";let e=t.match(/compatible;\s*([^/;\s]+)(?:\/[^\s;]*)?/i);return e&&e[1]?e[1].trim():t.trim().split("/")[0]?.trim().split(/\s+/)[0]?.trim()||"Other"}function h(t){let e=[],n=(t.headers.get("user-agent")||"").toLowerCase();if(!(n.includes("mozilla")||n.includes("chrome")||n.includes("safari")||n.includes("firefox")))return {score:0,signals:[],likely:false};t.headers.get("accept-language")||e.push("missing-accept-language"),t.headers.get("sec-fetch-mode")||e.push("missing-sec-fetch-mode");let r=t.headers.get("sec-ch-ua");r?r.toLowerCase().includes("headlesschrome")&&e.push("headless-chrome-hint"):e.push("missing-sec-ch-ua");let i=t.headers.get("accept")||"";(!i||i==="*/*")&&e.push("missing-or-bare-accept"),(t.headers.get("connection")||"").toLowerCase()==="close"&&e.push("connection-close");let o=e.length;return {score:o,signals:e,likely:o>=2}}function v(t){let e=S(t),n=d(t),s=g(t),r;return n?r="declared-crawler":s?r="coding-agent-hint":e==="Browser"?r="browser":r="other",{kind:r,label:e,isAiBot:n,codingAgentHint:s}}function m(t){let e=t.headers.get("user-agent")||"",n=v(e),s=h(t),r=n.kind,i=n.label;return r==="browser"&&s.likely&&(r="headless-likely",i="Headless"),{...n,kind:r,label:i,headless:s}}var f=class extends Error{constructor(e){super(e),this.name="HashSecretError";}};function y(){let t=globalThis.crypto;if(!t?.subtle)throw new f("Web Crypto is unavailable. agent-analytics requires Node >= 20, or any runtime exposing globalThis.crypto.subtle (Vercel Edge, Cloudflare Workers, Deno, browsers).");return t.subtle}var B=new Map;function z(t){let e=B.get(t);return e||(e=y().importKey("raw",new TextEncoder().encode(t),{name:"HMAC",hash:"SHA-256"},false,["sign"]),B.set(t,e)),e}async function b(t,e){if(typeof e!="string"||e.length===0)throw new f("hashId requires a non-empty secret");let n=await y().sign("HMAC",await z(e),new TextEncoder().encode(t)),s=new Uint8Array(n,0,8),r="";for(let i of s)r+=i.toString(16).padStart(2,"0");return "anon_"+r}function A(){let t=new Uint8Array(32);y(),globalThis.crypto.getRandomValues(t);let e="";for(let n of t)e+=n.toString(16).padStart(2,"0");return e}var x,E=false;function N(t){if(t)return t;let e=typeof process<"u"?process.env?.AGENT_ANALYTICS_ID_SECRET:void 0;return e||(x||(x=A(),E||(E=true,console.warn("[agent-analytics] No idSecret or AGENT_ANALYTICS_ID_SECRET set. Using a per-instance random secret: distinctIds will not correlate across instances or deploys."))),x)}async function G(t,e){let n=t.headers.get("user-agent")||"",s=e.onlyBots??false,r=e.skipBrowsers??false;if(!(s&&!d(n))&&!(r&&!d(n)&&!g(n)&&!h(t).likely))try{let i="/",o="";try{let w=new URL(t.url);i=w.pathname,o=w.origin;}catch{i=t.url||"/";}let p=e.origin??o,k=(t.headers.get("x-forwarded-for")||"").split(",")[0]?.trim()??"",_=t.headers.get("referer"),I=e.captureCountry&&(t.headers.get("x-vercel-ip-country")||t.headers.get("cf-ipcountry")||t.headers.get("x-country-code"))||null,P=e.captureGeo?O(t):null,a=m(t),l=e.verify?e.verify(t):null,H=a.kind==="headless-likely"||a.kind==="browser",R=await b(`${k}:${n}`,N(e.idSecret)),D={event:e.eventName??"agent_visit",distinctId:R,timestamp:new Date().toISOString(),properties:{...e.properties,$process_person_profile:!1,$current_url:p?`${p}${i}`:i,path:i,method:t.method,...e.captureCountry?{country_code:I}:{},...P??{},...e.captureIp?{client_ip:k||null}:{},user_agent:n,is_ai_bot:a.isAiBot,bot_name:a.label,ua_category:a.kind,coding_agent_hint:a.codingAgentHint,...H?{headless_score:a.headless?.score??0,headless_likely:a.headless?.likely??!1}:{},...l?{bot_verified:l.verified,bot_verification:l.verdict,...l.reason?{bot_verification_reason:l.reason}:{}}:{},referer:_,source:e.source??null}};await e.analytics.capture(D);}catch(i){e.onError?.(i instanceof Error?i:new Error(String(i)));}}function O(t){let e=r=>{if(!r)return "";try{return decodeURIComponent(r)}catch{return r}},n=[["region",e(t.headers.get("x-vercel-ip-country-region"))],["city",e(t.headers.get("x-vercel-ip-city"))],["latitude",t.headers.get("x-vercel-ip-latitude")??""],["longitude",t.headers.get("x-vercel-ip-longitude")??""],["timezone",t.headers.get("x-vercel-ip-timezone")??""]],s={};for(let[r,i]of n)i&&(s[r]=i);return s}var c=class extends Error{status;body;constructor(e,n,s){super(e),this.name="CaptureTransportError",this.status=n,this.body=s;}};function U(t){let e=t.host??"https://us.i.posthog.com",n=(/^https?:\/\//.test(e)?e:`https://${e}`).replace(/\/$/,""),s=(t.path??"/i/v0/e/").replace(/^(?!\/)/,"/"),r=`${n}${s}`,i=t.fetchImpl??fetch;return {async capture(o){let p={api_key:t.apiKey,event:o.event,distinct_id:o.distinctId,timestamp:o.timestamp,properties:o.properties},u=await i(r,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(p),keepalive:true,signal:AbortSignal.timeout(t.timeoutMs??3e3)});if(!u.ok)throw new c(`PostHog capture failed: ${u.status} ${u.statusText}`,u.status,await u.text().catch(()=>{}))}}}function L(t){let e=t.fetchImpl??fetch,n=t.transform??(s=>s);return {async capture(s){let r=await e(t.url,{method:"POST",headers:{"Content-Type":"application/json",...t.headers??{}},body:JSON.stringify(n(s)),keepalive:true,signal:AbortSignal.timeout(t.timeoutMs??3e3)});if(!r.ok)throw new c(`Webhook capture failed: ${r.status} ${r.statusText}`,r.status,await r.text().catch(()=>{}))}}}function $(t){return {capture:t}}
2
- export{C as AI_BOT_PATTERN,c as CaptureTransportError,T as HTTP_CLIENT_PATTERN,f as HashSecretError,v as classifyAgent,m as classifyRequest,$ as customAnalytics,h as detectHeadless,M as firstUserAgentProduct,b as hashId,d as isAiBot,g as isHttpClient,S as parseBotName,U as posthogAnalytics,A as randomSecret,G as trackVisit,L as webhookAnalytics};//# sourceMappingURL=index.js.map
1
+ var B=/ClaudeBot|Claude-User|Claude-SearchBot|Claude-Web|Anthropic|GPTBot|ChatGPT-User|OAI-SearchBot|PerplexityBot|Perplexity-User|Google-Extended|Google-CloudVertexBot|Google-Agent|GoogleAgent-Mariner|Gemini-Deep-Research|Applebot|cohere|Bytespider|CCBot|Amazonbot|Amzn-SearchBot|NovaAct|AzureAI-SearchBot|Meta-ExternalAgent|meta-externalfetcher|meta-webindexer|FacebookBot|DuckAssistBot|MistralAI-User|YouBot|AI2Bot|Diffbot|DeepSeek|PanguBot|Webzio-Extended|omgili|Timpibot|Grok|Manus-User|quillbot|MyCentralAIScraperBot|Cursor|Windsurf/i,S=/axios\/|curl\/|(?:^|[\s(])got(?:\/|[\s(])|\bcolly\b|Electron\/|node-fetch\/|python-requests\/|Go-http-client\/|okhttp\/|aiohttp\/|Deno\//i;function f(t){return t?B.test(t):false}function m(t){return t?S.test(t):false}function T(t){if(!t||typeof t!="string")return "Other";let e=t.toLowerCase();return e.includes("chatgpt-user")||e.includes("gptbot")||e.includes("oai-searchbot")||e.includes("openai")?"ChatGPT":e.includes("claudebot")||e.includes("claude-user")||e.includes("claude-searchbot")||e.includes("claude-web")||e.includes("anthropic")?"Claude":e.includes("perplexitybot")||e.includes("perplexity-user")?"Perplexity":e.includes("ccbot")?"Common Crawl":e.includes("google-extended")||e.includes("googlebot")||e.includes("google-cloudvertexbot")||e.includes("google-agent")||e.includes("googleagent-mariner")||e.includes("gemini-deep-research")?"Google":e.includes("applebot")?"Apple":e.includes("bingbot")?"Bing":e.includes("bytespider")?"Bytespider":e.includes("amazonbot")||e.includes("amzn-searchbot")||e.includes("novaact")?"Amazon":e.includes("meta-externalagent")||e.includes("meta-externalfetcher")||e.includes("meta-webindexer")||e.includes("facebookbot")?"Meta":e.includes("mistralai-user")?"Mistral":e.includes("duckassistbot")?"DuckDuckGo":e.includes("youbot")?"You.com":e.includes("diffbot")?"Diffbot":e.includes("ai2bot")?"AI2":e.includes("cohere")?"Cohere":e.includes("cursor")?"Cursor":e.includes("windsurf")?"Windsurf":e.includes("deepseek")?"DeepSeek":e.includes("pangubot")?"Huawei":e.includes("webzio")||e.includes("omgili")?"Webz.io":e.includes("timpibot")?"Timpi":e.includes("grok")||e.includes("xai-")?"xAI":e.includes("manus-user")?"Manus":e.includes("quillbot")?"QuillBot":e.includes("azureai-searchbot")?"Microsoft":e.includes("mycentralaiscraperbot")?"MyCentralAI":e.includes("petalbot")?"PetalBot":e.includes("ahrefsbot")?"Ahrefs":e.includes("semrushbot")?"Semrush":e.includes("mj12bot")?"Majestic":e.includes("dotbot")||e.includes("rogerbot")?"Moz":e.includes("screaming frog")?"Screaming Frog":e.includes("sitebulb")?"Sitebulb":e.includes("linkfluence")?"Linkfluence":e.includes("dataforseo")?"DataForSEO":e.includes("serpstatbot")?"Serpstat":e.includes("uptimerobot")?"UptimeRobot":e.includes("pingdom")?"Pingdom":e.includes("statuscake")?"StatusCake":e.includes("newrelicpinger")?"New Relic":e.includes("datadogagent")||e.includes("datadog")?"Datadog":e.includes("slackbot")?"Slack":e.includes("twitterbot")?"Twitter":e.includes("linkedinbot")?"LinkedIn":e.includes("discordbot")?"Discord":e.includes("telegrambot")?"Telegram":e.includes("whatsapp")?"WhatsApp":e.includes("linkupbot")?"Linkup":e.includes("sogou")?"Sogou":e.includes("yandexbot")?"Yandex":e.includes("baiduspider")?"Baidu":e.includes("facebookexternalhit")?"Facebook":e.includes("com.apple.webkit")?"Apple URL Preview":e.includes("ohdear")?"Oh Dear":e.includes("scrapy")?"Scrapy":e.includes("headlesschrome")?"Headless Chrome":e.includes("phantomjs")?"PhantomJS":e.includes("wget")?"wget":e.includes("httpie")?"HTTPie":e.includes("guzzlehttp")?"Guzzle":e.includes("electron/")?"Electron":/curl\//.test(e)?"curl":/axios\//.test(e)?"axios":/(?:^|[\s(])got(?:\/|[\s(])/.test(e)?"got":/\bcolly\b/.test(e)?"colly":/node-fetch\//.test(e)?"node-fetch":/python-requests\//.test(e)?"python-requests":/go-http-client\//.test(e)?"Go http client":/okhttp\//.test(e)?"OkHttp":/aiohttp\//.test(e)?"aiohttp":/deno\//.test(e)?"Deno":e.includes("mozilla")||e.includes("chrome")||e.includes("safari")||e.includes("firefox")?"Browser":"Other"}function M(t){if(!t||typeof t!="string")return "Other";let e=t.match(/compatible;\s*([^/;\s]+)(?:\/[^\s;]*)?/i);return e&&e[1]?e[1].trim():t.trim().split("/")[0]?.trim().split(/\s+/)[0]?.trim()||"Other"}function b(t){let e=[],r=(t.headers.get("user-agent")||"").toLowerCase();if(!(r.includes("mozilla")||r.includes("chrome")||r.includes("safari")||r.includes("firefox")))return {score:0,signals:[],likely:false};t.headers.get("accept-language")||e.push("missing-accept-language"),t.headers.get("sec-fetch-mode")||e.push("missing-sec-fetch-mode");let n=t.headers.get("sec-ch-ua");n?n.toLowerCase().includes("headlesschrome")&&e.push("headless-chrome-hint"):e.push("missing-sec-ch-ua");let i=t.headers.get("accept")||"";(!i||i==="*/*")&&e.push("missing-or-bare-accept"),(t.headers.get("connection")||"").toLowerCase()==="close"&&e.push("connection-close");let s=e.length;return {score:s,signals:e,likely:s>=2}}function v(t){let e=T(t),r=f(t),o=m(t),n;return r?n="declared-crawler":o?n="coding-agent-hint":e==="Browser"?n="browser":n="other",{kind:n,label:e,isAiBot:r,codingAgentHint:o}}function p(t){let e=t.headers.get("user-agent")||"",r=v(e),o=b(t),n=r.kind,i=r.label;return n==="browser"&&o.likely&&(n="headless-likely",i="Headless"),{...r,kind:n,label:i,headless:o}}var g=class extends Error{constructor(e){super(e),this.name="HashSecretError";}};function A(){let t=globalThis.crypto;if(!t?.subtle)throw new g("Web Crypto is unavailable. agent-analytics requires Node >= 20, or any runtime exposing globalThis.crypto.subtle (Vercel Edge, Cloudflare Workers, Deno, browsers).");return t.subtle}var E=new Map;function N(t){let e=E.get(t);return e||(e=A().importKey("raw",new TextEncoder().encode(t),{name:"HMAC",hash:"SHA-256"},false,["sign"]),E.set(t,e)),e}async function x(t,e){if(typeof e!="string"||e.length===0)throw new g("hashId requires a non-empty secret");let r=await A().sign("HMAC",await N(e),new TextEncoder().encode(t)),o=new Uint8Array(r,0,8),n="";for(let i of o)n+=i.toString(16).padStart(2,"0");return "anon_"+n}function w(){let t=new Uint8Array(32);A(),globalThis.crypto.getRandomValues(t);let e="";for(let r of t)e+=r.toString(16).padStart(2,"0");return e}var k,I=false;function z(t){if(t)return t;let e=typeof process<"u"?process.env?.AGENT_ANALYTICS_ID_SECRET:void 0;return e||(k||(k=w(),I||(I=true,console.warn("[agent-analytics] No idSecret or AGENT_ANALYTICS_ID_SECRET set. Using a per-instance random secret: distinctIds will not correlate across instances or deploys."))),k)}async function O(t,e){let r=t.headers.get("user-agent")||"",o=e.onlyBots??false,n=e.skipBrowsers??false;if(!(o&&!f(r))&&!(n&&!f(r)&&!m(r)&&!b(t).likely))try{let i="/",s="";try{let C=new URL(t.url);i=C.pathname,s=C.origin;}catch{i=t.url||"/";}let a=e.origin??s,y=(t.headers.get("x-forwarded-for")||"").split(",")[0]?.trim()??"",h=t.headers.get("referer"),R=e.captureCountry&&(t.headers.get("x-vercel-ip-country")||t.headers.get("cf-ipcountry")||t.headers.get("x-country-code"))||null,_=e.captureGeo?U(t):null,c=p(t),d=e.verify?await e.verify(t):null,D=c.kind==="headless-likely"||c.kind==="browser",H=await x(`${y}:${r}`,z(e.idSecret)),G={event:e.eventName??"agent_visit",distinctId:H,timestamp:new Date().toISOString(),properties:{...e.properties,$process_person_profile:!1,$current_url:a?`${a}${i}`:i,path:i,method:t.method,...e.captureCountry?{country_code:R}:{},..._??{},...e.captureIp?{client_ip:y||null}:{},user_agent:r,is_ai_bot:c.isAiBot,bot_name:c.label,ua_category:c.kind,coding_agent_hint:c.codingAgentHint,...D?{headless_score:c.headless?.score??0,headless_likely:c.headless?.likely??!1}:{},...d?{bot_verified:d.verified,bot_verification:d.verdict,...d.reason?{bot_verification_reason:d.reason}:{}}:{},referer:h,source:e.source??null}};await e.analytics.capture(G);}catch(i){e.onError?.(i instanceof Error?i:new Error(String(i)));}}function U(t){let e=n=>{if(!n)return "";try{return decodeURIComponent(n)}catch{return n}},r=[["region",e(t.headers.get("x-vercel-ip-country-region"))],["city",e(t.headers.get("x-vercel-ip-city"))],["latitude",t.headers.get("x-vercel-ip-latitude")??""],["longitude",t.headers.get("x-vercel-ip-longitude")??""],["timezone",t.headers.get("x-vercel-ip-timezone")??""]],o={};for(let[n,i]of r)i&&(o[n]=i);return o}var u=class extends Error{status;body;constructor(e,r,o){super(e),this.name="CaptureTransportError",this.status=r,this.body=o;}};var L=/ChatGPT-User|OAI-SearchBot|Claude-User|Claude-SearchBot|Perplexity-User|claude-code|DuckAssistBot|MistralAI-User|Gemini-Deep-Research|Manus-User|YouBot/i,$=/GPTBot|ClaudeBot|Claude-Web|CCBot|Bytespider|Amazonbot|Amzn-SearchBot|Meta-ExternalAgent|meta-externalfetcher|meta-webindexer|FacebookBot|Google-Extended|Applebot-Extended|AI2Bot|Diffbot|omgili|Webzio-Extended|Timpibot|PanguBot|cohere|DeepSeek|Grok|quillbot|MyCentralAIScraperBot|NovaAct|AzureAI-SearchBot|Google-CloudVertexBot/i,j=/bingbot|Googlebot|DuckDuckBot|YandexBot|Baiduspider|PetalBot|Sogou|Applebot(?!-Extended)/i;function P(t){let e=t??"";return e?L.test(e)?"retrieval":$.test(e)?"training":j.test(e)?"search":"unknown":"unknown"}function V(t,e={}){let r=t.headers.get("user-agent")||"",o=p(t),n=o.label,i=P(r);if(i==="unknown"&&o.codingAgentHint&&(i="tooling"),e.allowList?.some(h=>h===n||r.toLowerCase().includes(h.toLowerCase())))return {action:"allow",intent:i,label:n,reason:"on allowList"};let a;return e.verify&&(a=e.verify(t).verdict,a==="spoofed")?{action:"block",intent:i,label:n,verification:a,reason:`${n} claimed but client IP is outside its published ranges`}:{action:i==="training"?e.onTraining??"meter":i==="retrieval"?e.onRetrieval??"allow":i==="search"?e.onSearch??"allow":i==="tooling"?e.onTooling??"allow":"allow",intent:i,label:n,...a?{verification:a}:{},reason:{retrieval:"a person is waiting on this answer",training:"bulk corpus collection",search:"search index crawler",tooling:"coding agent or HTTP client",unknown:"not a recognised agent"}[i]}}function W(t){let e=t.host??"https://us.i.posthog.com",r=(/^https?:\/\//.test(e)?e:`https://${e}`).replace(/\/$/,""),o=(t.path??"/i/v0/e/").replace(/^(?!\/)/,"/"),n=`${r}${o}`,i=t.fetchImpl??fetch;return {async capture(s){let a={api_key:t.apiKey,event:s.event,distinct_id:s.distinctId,timestamp:s.timestamp,properties:s.properties},l=await i(n,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(a),keepalive:true,signal:AbortSignal.timeout(t.timeoutMs??3e3)});if(!l.ok)throw new u(`PostHog capture failed: ${l.status} ${l.statusText}`,l.status,await l.text().catch(()=>{}))}}}function K(t){let e=t.fetchImpl??fetch,r=t.transform??(o=>o);return {async capture(o){let n=await e(t.url,{method:"POST",headers:{"Content-Type":"application/json",...t.headers??{}},body:JSON.stringify(r(o)),keepalive:true,signal:AbortSignal.timeout(t.timeoutMs??3e3)});if(!n.ok)throw new u(`Webhook capture failed: ${n.status} ${n.statusText}`,n.status,await n.text().catch(()=>{}))}}}function F(t){return {capture:t}}
2
+ export{B as AI_BOT_PATTERN,u as CaptureTransportError,S as HTTP_CLIENT_PATTERN,g as HashSecretError,P as agentIntent,V as agentPolicy,v as classifyAgent,p as classifyRequest,F as customAnalytics,b as detectHeadless,M as firstUserAgentProduct,x as hashId,f as isAiBot,m as isHttpClient,T as parseBotName,W as posthogAnalytics,w as randomSecret,O as trackVisit,K as webhookAnalytics};//# sourceMappingURL=index.js.map
3
3
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/bots.ts","../src/hash.ts","../src/track.ts","../src/errors.ts","../src/adapters/posthog.ts","../src/adapters/webhook.ts","../src/adapters/custom.ts"],"names":["AI_BOT_PATTERN","HTTP_CLIENT_PATTERN","isAiBot","userAgent","isHttpClient","parseBotName","s","firstUserAgentProduct","compatibleMatch","detectHeadless","req","signals","ua","secChUa","accept","score","classifyAgent","label","aiBot","httpClient","kind","classifyRequest","base","headless","HashSecretError","message","subtle","c","KEYS","keyFor","secret","k","hashId","input","sig","bytes","out","b","randomSecret","x","fallbackSecret","warnedNoSecret","resolveSecret","explicit","fromEnv","trackVisit","opts","onlyBots","skipBrowsers","pathname","originFromUrl","url","origin","ip","referer","country","geo","extractGeo","classification","verification","headlessMeaningful","distinctId","event","err","decode","v","fields","CaptureTransportError","status","body","posthogAnalytics","config","hostRaw","path","endpoint","fetchImpl","payload","res","webhookAnalytics","transform","e","customAnalytics","capture"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * User-agent substrings that identify **publicly declared** AI crawlers — the\n * branded bots that identify themselves by name (OpenAI's GPTBot, Anthropic's\n * ClaudeBot, Perplexity-User, Google-Extended, etc.). High-confidence: when\n * this matches, the request almost certainly comes from that vendor's crawler\n * fleet.\n *\n * Does NOT include **coding-agent traffic** (Claude Code, Cline, Cursor,\n * Windsurf, Aider, OpenCode, VS Code). Those tools use generic HTTP library\n * UAs (axios, curl, got, colly, Electron) or spoof full browser UAs — they\n * can't be distinguished from non-AI traffic by UA alone. See\n * {@link HTTP_CLIENT_PATTERN} for the loose heuristic layer.\n *\n * Sources consulted when updating: darkvisitors.com, vendor docs from OpenAI,\n * Anthropic, Google, Perplexity, Cohere, Apple, Bytedance.\n */\nexport const AI_BOT_PATTERN =\n /ClaudeBot|Claude-User|Claude-SearchBot|Claude-Web|Anthropic|GPTBot|ChatGPT-User|OAI-SearchBot|PerplexityBot|Perplexity-User|Google-Extended|Google-CloudVertexBot|Google-Agent|GoogleAgent-Mariner|Gemini-Deep-Research|Applebot|cohere|Bytespider|CCBot|Amazonbot|Amzn-SearchBot|NovaAct|AzureAI-SearchBot|Meta-ExternalAgent|meta-externalfetcher|meta-webindexer|FacebookBot|DuckAssistBot|MistralAI-User|YouBot|AI2Bot|Diffbot|DeepSeek|PanguBot|Webzio-Extended|omgili|Timpibot|Grok|Manus-User|quillbot|MyCentralAIScraperBot|Cursor|Windsurf/i\n\n/**\n * HTTP library / runtime signatures frequently used by coding agents. Matching\n * any of these is a **loose** signal — legitimate curl scripts, CI jobs, and\n * server-to-server traffic use the same libraries. Use this for the wider\n * net (`coding_agent_hint: true`) and pair with other signals (request\n * shape, JA4 fingerprint, path patterns) for higher confidence.\n *\n * Based on behavioural signatures observed by Addy Osmani:\n * Claude Code → axios/1.8.4\n * Cline, Junie → curl/8.4.0\n * Cursor → got (sindresorhus/got)\n * Windsurf → colly\n * VS Code → Electron / Chromium\n *\n * Aider and OpenCode use Playwright-driven full Mozilla/Safari UAs and are\n * indistinguishable from real browsers at the UA layer.\n */\nexport const HTTP_CLIENT_PATTERN =\n /axios\\/|curl\\/|(?:^|[\\s(])got(?:\\/|[\\s(])|\\bcolly\\b|Electron\\/|node-fetch\\/|python-requests\\/|Go-http-client\\/|okhttp\\/|aiohttp\\/|Deno\\//i\n\nexport function isAiBot(userAgent: string | null | undefined): boolean {\n if (!userAgent) return false\n return AI_BOT_PATTERN.test(userAgent)\n}\n\nexport function isHttpClient(userAgent: string | null | undefined): boolean {\n if (!userAgent) return false\n return HTTP_CLIENT_PATTERN.test(userAgent)\n}\n\n/**\n * Map a user-agent string to a coarse, human-readable label. Returns one of:\n *\n * - A branded-crawler name (`'Claude'`, `'ChatGPT'`, …) — pair with\n * {@link isAiBot} for `is_ai_bot: true` segmentation.\n * - An HTTP-library name (`'curl'`, `'axios'`, `'got'`, `'colly'`,\n * `'Electron'`, …) — hint of a coding agent or automation; not\n * conclusive. Pair with {@link isHttpClient}.\n * - `'Browser'` for typical desktop browsers (possibly spoofed by\n * Playwright-based agents like Aider/OpenCode — this label alone can't\n * tell you).\n * - `'Other'` for anything unrecognised or empty input.\n */\nexport function parseBotName(userAgent: string | null | undefined): string {\n if (!userAgent || typeof userAgent !== 'string') return 'Other'\n const s = userAgent.toLowerCase()\n\n // Publicly declared AI crawlers (high confidence).\n if (s.includes('chatgpt-user') || s.includes('gptbot') || s.includes('oai-searchbot') || s.includes('openai'))\n return 'ChatGPT'\n if (\n s.includes('claudebot') ||\n s.includes('claude-user') ||\n s.includes('claude-searchbot') ||\n s.includes('claude-web') ||\n s.includes('anthropic')\n )\n return 'Claude'\n if (s.includes('perplexitybot') || s.includes('perplexity-user')) return 'Perplexity'\n if (s.includes('ccbot')) return 'Common Crawl'\n if (\n s.includes('google-extended') ||\n s.includes('googlebot') ||\n s.includes('google-cloudvertexbot') ||\n s.includes('google-agent') ||\n s.includes('googleagent-mariner') ||\n s.includes('gemini-deep-research')\n )\n return 'Google'\n if (s.includes('applebot')) return 'Apple'\n if (s.includes('bingbot')) return 'Bing'\n if (s.includes('bytespider')) return 'Bytespider'\n if (s.includes('amazonbot') || s.includes('amzn-searchbot') || s.includes('novaact')) return 'Amazon'\n if (\n s.includes('meta-externalagent') ||\n s.includes('meta-externalfetcher') ||\n s.includes('meta-webindexer') ||\n s.includes('facebookbot')\n )\n return 'Meta'\n if (s.includes('mistralai-user')) return 'Mistral'\n if (s.includes('duckassistbot')) return 'DuckDuckGo'\n if (s.includes('youbot')) return 'You.com'\n if (s.includes('diffbot')) return 'Diffbot'\n if (s.includes('ai2bot')) return 'AI2'\n if (s.includes('cohere')) return 'Cohere'\n if (s.includes('cursor')) return 'Cursor'\n if (s.includes('windsurf')) return 'Windsurf'\n if (s.includes('deepseek')) return 'DeepSeek'\n if (s.includes('pangubot')) return 'Huawei'\n if (s.includes('webzio') || s.includes('omgili')) return 'Webz.io'\n if (s.includes('timpibot')) return 'Timpi'\n if (s.includes('grok') || s.includes('xai-')) return 'xAI'\n if (s.includes('manus-user')) return 'Manus'\n if (s.includes('quillbot')) return 'QuillBot'\n if (s.includes('azureai-searchbot')) return 'Microsoft'\n if (s.includes('mycentralaiscraperbot')) return 'MyCentralAI'\n if (s.includes('petalbot')) return 'PetalBot'\n\n // SEO crawlers and monitoring bots.\n if (s.includes('ahrefsbot')) return 'Ahrefs'\n if (s.includes('semrushbot')) return 'Semrush'\n if (s.includes('mj12bot')) return 'Majestic'\n if (s.includes('dotbot')) return 'Moz'\n if (s.includes('rogerbot')) return 'Moz'\n if (s.includes('screaming frog')) return 'Screaming Frog'\n if (s.includes('sitebulb')) return 'Sitebulb'\n if (s.includes('linkfluence')) return 'Linkfluence'\n if (s.includes('dataforseo')) return 'DataForSEO'\n if (s.includes('serpstatbot')) return 'Serpstat'\n\n // Monitoring and feed bots.\n if (s.includes('uptimerobot')) return 'UptimeRobot'\n if (s.includes('pingdom')) return 'Pingdom'\n if (s.includes('statuscake')) return 'StatusCake'\n if (s.includes('newrelicpinger')) return 'New Relic'\n if (s.includes('datadogagent') || s.includes('datadog')) return 'Datadog'\n if (s.includes('slackbot')) return 'Slack'\n if (s.includes('twitterbot')) return 'Twitter'\n if (s.includes('linkedinbot')) return 'LinkedIn'\n if (s.includes('discordbot')) return 'Discord'\n if (s.includes('telegrambot')) return 'Telegram'\n if (s.includes('whatsapp')) return 'WhatsApp'\n\n // AI search and indexing bots.\n if (s.includes('linkupbot')) return 'Linkup'\n if (s.includes('sogou')) return 'Sogou'\n if (s.includes('yandexbot')) return 'Yandex'\n if (s.includes('baiduspider')) return 'Baidu'\n\n // Link preview fetchers.\n if (s.includes('facebookexternalhit')) return 'Facebook'\n if (s.includes('com.apple.webkit')) return 'Apple URL Preview'\n\n // Uptime and monitoring.\n if (s.includes('ohdear')) return 'Oh Dear'\n\n // Generic scrapers.\n if (s.includes('scrapy')) return 'Scrapy'\n if (s.includes('headlesschrome')) return 'Headless Chrome'\n if (s.includes('phantomjs')) return 'PhantomJS'\n if (s.includes('wget')) return 'wget'\n if (s.includes('httpie')) return 'HTTPie'\n if (s.includes('guzzlehttp')) return 'Guzzle'\n\n // HTTP library / runtime signatures (loose — coding agent or automation).\n // Check Electron before Browser since Electron UAs contain Chrome/Safari.\n if (s.includes('electron/')) return 'Electron'\n if (/curl\\//.test(s)) return 'curl'\n if (/axios\\//.test(s)) return 'axios'\n if (/(?:^|[\\s(])got(?:\\/|[\\s(])/.test(s)) return 'got'\n if (/\\bcolly\\b/.test(s)) return 'colly'\n if (/node-fetch\\//.test(s)) return 'node-fetch'\n if (/python-requests\\//.test(s)) return 'python-requests'\n if (/go-http-client\\//.test(s)) return 'Go http client'\n if (/okhttp\\//.test(s)) return 'OkHttp'\n if (/aiohttp\\//.test(s)) return 'aiohttp'\n if (/deno\\//.test(s)) return 'Deno'\n\n // Real browsers (or UAs spoofed to look like them — see Aider/OpenCode note).\n if (s.includes('mozilla') || s.includes('chrome') || s.includes('safari') || s.includes('firefox'))\n return 'Browser'\n\n return 'Other'\n}\n\n/**\n * Return the first product token from a UA header, useful for segmenting by\n * client without hard-coding every bot name. Falls back to `'Other'` for empty\n * input.\n */\nexport function firstUserAgentProduct(userAgent: string | null | undefined): string {\n if (!userAgent || typeof userAgent !== 'string') return 'Other'\n const compatibleMatch = userAgent.match(/compatible;\\s*([^/;\\s]+)(?:\\/[^\\s;]*)?/i)\n if (compatibleMatch && compatibleMatch[1]) return compatibleMatch[1].trim()\n const first = userAgent.trim().split('/')[0]?.trim().split(/\\s+/)[0]?.trim()\n return first || 'Other'\n}\n\n/**\n * Detect likely headless/automated browsers by checking for missing headers\n * that real browsers always send. Playwright, Puppeteer, and similar tools\n * spoof the UA but often omit standard browser headers.\n *\n * Signals checked (each scores 1 point):\n * - Missing `Accept-Language` — every real browser sends this\n * - Missing `Sec-Fetch-Mode` — sent by all modern browsers\n * - Missing `Sec-CH-UA` — Client Hints, Chromium 89+\n * - `Sec-CH-UA` contains \"HeadlessChrome\"\n * - Missing or bare Accept header — browsers send detailed accept lists\n * - `Connection: close` with browser UA — browsers use keep-alive\n *\n * Returns a score (0-6), the signals that fired, and a boolean `likely`\n * flag (score >= 2 with a browser-like UA).\n */\nexport function detectHeadless(req: Request): HeadlessDetection {\n const signals: string[] = []\n const ua = (req.headers.get('user-agent') || '').toLowerCase()\n const isBrowserUA =\n ua.includes('mozilla') || ua.includes('chrome') || ua.includes('safari') || ua.includes('firefox')\n\n if (!isBrowserUA) return { score: 0, signals: [], likely: false }\n\n if (!req.headers.get('accept-language')) {\n signals.push('missing-accept-language')\n }\n if (!req.headers.get('sec-fetch-mode')) {\n signals.push('missing-sec-fetch-mode')\n }\n const secChUa = req.headers.get('sec-ch-ua')\n if (!secChUa) {\n signals.push('missing-sec-ch-ua')\n } else if (secChUa.toLowerCase().includes('headlesschrome')) {\n signals.push('headless-chrome-hint')\n }\n const accept = req.headers.get('accept') || ''\n if (!accept || accept === '*/*') {\n signals.push('missing-or-bare-accept')\n }\n if ((req.headers.get('connection') || '').toLowerCase() === 'close') {\n signals.push('connection-close')\n }\n\n const score = signals.length\n return { score, signals, likely: score >= 2 }\n}\n\nexport interface HeadlessDetection {\n /** Number of suspicious signals found (0-6). */\n score: number\n /** Names of the specific signals that fired. */\n signals: string[]\n /** True when score >= 2 — strong headless indication. */\n likely: boolean\n}\n\nexport type AgentKind =\n | 'declared-crawler'\n | 'coding-agent-hint'\n | 'headless-likely'\n | 'browser'\n | 'other'\n\nexport interface AgentClassification {\n /**\n * Categorical tag for the request:\n *\n * - `'declared-crawler'` — {@link AI_BOT_PATTERN} matched. High confidence.\n * - `'coding-agent-hint'` — {@link HTTP_CLIENT_PATTERN} matched. Loose\n * signal; could be a coding agent, a curl script, or any automation.\n * - `'headless-likely'` — Browser-like UA but missing standard headers.\n * Strong signal of Playwright/Puppeteer automation (Aider, OpenCode, etc.).\n * - `'browser'` — Looks like a real browser with expected headers present.\n * - `'other'` — Unrecognised or empty.\n */\n kind: AgentKind\n /** Human-readable label, same string {@link parseBotName} returns. */\n label: string\n /** Strict: `true` only when the UA matches a branded AI crawler. */\n isAiBot: boolean\n /** Loose: `true` for known HTTP-library / automation UAs. */\n codingAgentHint: boolean\n /** Headless browser detection result. Only populated when `req` is passed. */\n headless?: HeadlessDetection\n}\n\n/**\n * UA-only classification. Use {@link classifyRequest} for full detection\n * including headless browser heuristics.\n */\nexport function classifyAgent(userAgent: string | null | undefined): AgentClassification {\n const label = parseBotName(userAgent)\n const aiBot = isAiBot(userAgent)\n const httpClient = isHttpClient(userAgent)\n\n let kind: AgentKind\n if (aiBot) kind = 'declared-crawler'\n else if (httpClient) kind = 'coding-agent-hint'\n else if (label === 'Browser') kind = 'browser'\n else kind = 'other'\n\n return { kind, label, isAiBot: aiBot, codingAgentHint: httpClient }\n}\n\n/**\n * Full request classification — combines UA parsing with header-based\n * headless detection. When a browser-like UA is missing standard headers,\n * the kind is promoted from `'browser'` to `'headless-likely'`.\n */\nexport function classifyRequest(req: Request): AgentClassification {\n const userAgent = req.headers.get('user-agent') || ''\n const base = classifyAgent(userAgent)\n const headless = detectHeadless(req)\n\n let kind = base.kind\n let label = base.label\n if (kind === 'browser' && headless.likely) {\n kind = 'headless-likely'\n // Relabel too. Leaving it as 'Browser' meant automation with a spoofed\n // browser UA — 79% of one production site's agent traffic — was\n // indistinguishable from a human in any `bot_name` breakdown, and was\n // silently excluded by the obvious `bot_name != 'Browser'` filter.\n label = 'Headless'\n }\n\n return { ...base, kind, label, headless }\n}\n","/**\n * Keyed, non-reversible anonymous identifiers.\n *\n * The previous implementation was an unsalted 32-bit djb2 over `ip:userAgent`.\n * Because the user agent is emitted in plaintext on the same event, an attacker\n * held half the preimage and only had to search the IPv4 space — recovering a\n * residential IP took 75 seconds single-threaded. That is pseudonymisation, not\n * anonymisation, and it does not survive GDPR Recital 26.\n *\n * This uses HMAC-SHA-256 with a caller-supplied secret, truncated to 64 bits.\n * Web Crypto is available on Vercel Edge, Cloudflare Workers, Deno and Node 18+.\n */\n\n/** Thrown when a secret is missing or unusable. */\nexport class HashSecretError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'HashSecretError'\n }\n}\n\n/**\n * Web Crypto, or a clear error explaining why it is missing.\n *\n * `globalThis.crypto` is present by default from Node 19; on Node 18 it sat\n * behind `--experimental-global-webcrypto`. We require Node >= 20 rather than\n * shipping a `node:crypto` fallback, because a static import of a Node builtin\n * breaks bundling for the edge runtimes this library primarily targets — and\n * Node 18 reached end of life in April 2025.\n */\nfunction subtle(): SubtleCrypto {\n const c = globalThis.crypto\n if (!c?.subtle) {\n throw new HashSecretError(\n 'Web Crypto is unavailable. agent-analytics requires Node >= 20, or any ' +\n 'runtime exposing globalThis.crypto.subtle (Vercel Edge, Cloudflare ' +\n 'Workers, Deno, browsers).'\n )\n }\n return c.subtle\n}\n\n// Importing a CryptoKey costs more than the signature itself, so keep one per\n// secret. Bounded by however many secrets a process configures — realistically\n// one.\nconst KEYS = new Map<string, Promise<CryptoKey>>()\n\nfunction keyFor(secret: string): Promise<CryptoKey> {\n let k = KEYS.get(secret)\n if (!k) {\n k = subtle().importKey(\n 'raw',\n new TextEncoder().encode(secret),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign']\n )\n KEYS.set(secret, k)\n }\n return k\n}\n\n/**\n * Hash `input` under `secret`, returning `anon_` followed by 16 hex characters\n * (64 bits — collision-free well past any realistic distinct-visitor count).\n *\n * The secret must be stable across instances for identifiers to be comparable\n * over time, and secret from anyone who can read your events: publishing it\n * makes the identifier exactly as reversible as the old implementation was.\n * Rotating it deliberately breaks continuity, which is correct behaviour for a\n * privacy-preserving id.\n */\nexport async function hashId(input: string, secret: string): Promise<string> {\n if (typeof secret !== 'string' || secret.length === 0) {\n throw new HashSecretError('hashId requires a non-empty secret')\n }\n const sig = await subtle().sign('HMAC', await keyFor(secret), new TextEncoder().encode(input))\n const bytes = new Uint8Array(sig, 0, 8)\n let out = ''\n for (const b of bytes) out += b.toString(16).padStart(2, '0')\n return 'anon_' + out\n}\n\n/**\n * Generate a random secret. Used as the default when none is configured, so the\n * privacy-preserving path is the one you get by doing nothing. Identifiers are\n * then only stable within a single instance's lifetime — set a real secret when\n * you need them comparable across instances and over time.\n */\nexport function randomSecret(): string {\n const b = new Uint8Array(32)\n subtle() // surface the same clear error if Web Crypto is missing\n globalThis.crypto.getRandomValues(b)\n let out = ''\n for (const x of b) out += x.toString(16).padStart(2, '0')\n return out\n}\n","import { classifyRequest, detectHeadless, isAiBot, isHttpClient } from './bots.js'\nimport { hashId, randomSecret } from './hash.js'\nimport type { TrackVisitOptions } from './types.js'\n\n/**\n * Fallback secret, generated once per instance. Keeps the default path\n * privacy-preserving rather than making callers opt in to safety, at the cost\n * of identifiers that only correlate within one instance's lifetime.\n */\nlet fallbackSecret: string | undefined\nlet warnedNoSecret = false\n\nfunction resolveSecret(explicit: string | undefined): string {\n if (explicit) return explicit\n const fromEnv =\n typeof process !== 'undefined' ? process.env?.AGENT_ANALYTICS_ID_SECRET : undefined\n if (fromEnv) return fromEnv\n if (!fallbackSecret) {\n fallbackSecret = randomSecret()\n if (!warnedNoSecret) {\n warnedNoSecret = true\n // Once per instance, not once per request.\n console.warn(\n '[agent-analytics] No idSecret or AGENT_ANALYTICS_ID_SECRET set. ' +\n 'Using a per-instance random secret: distinctIds will not correlate ' +\n 'across instances or deploys.'\n )\n }\n }\n return fallbackSecret\n}\n\n/**\n * Capture an event describing the incoming request. Fire-and-forget: awaits the\n * adapter but routes errors to {@link TrackVisitOptions.onError} rather than\n * letting them reach the response path. Callers typically don't await it.\n *\n * By default, captures every request so coding-agent traffic (axios, curl,\n * Electron, …) shows up alongside branded crawlers. Set `onlyBots: true` to\n * restrict capture to UAs matching {@link AI_BOT_PATTERN}.\n */\nexport async function trackVisit(req: Request, opts: TrackVisitOptions): Promise<void> {\n const userAgent = req.headers.get('user-agent') || ''\n\n const onlyBots = opts.onlyBots ?? false\n const skipBrowsers = opts.skipBrowsers ?? false\n if (onlyBots && !isAiBot(userAgent)) return\n if (skipBrowsers && !isAiBot(userAgent) && !isHttpClient(userAgent)) {\n // Not a declared bot or HTTP client — check headless heuristics.\n // Playwright-based agents (Aider, OpenCode) will pass if they're missing\n // standard browser headers. Real browsers get skipped.\n if (!detectHeadless(req).likely) return\n }\n\n try {\n let pathname = '/'\n let originFromUrl = ''\n try {\n const url = new URL(req.url)\n pathname = url.pathname\n originFromUrl = url.origin\n } catch {\n // Some runtimes hand us a relative URL; fall back to the raw string.\n pathname = req.url || '/'\n }\n const origin = opts.origin ?? originFromUrl\n\n const forwardedFor = req.headers.get('x-forwarded-for') || ''\n const ip = forwardedFor.split(',')[0]?.trim() ?? ''\n const referer = req.headers.get('referer')\n const country = opts.captureCountry\n ? req.headers.get('x-vercel-ip-country') ||\n req.headers.get('cf-ipcountry') ||\n req.headers.get('x-country-code') ||\n null\n : null\n const geo = opts.captureGeo ? extractGeo(req) : null\n const classification = classifyRequest(req)\n\n // Verification is injected rather than imported, so the published IP range\n // tables only reach bundles that actually use them. Import `verifyRequest`\n // from `@apideck/agent-analytics/verify` and pass it as `verify`.\n const verification = opts.verify ? opts.verify(req) : null\n\n // Headless scoring only discriminates for browser-shaped UAs. On a declared\n // crawler or an HTTP client it fires on nearly everything — measured true on\n // 99% of captured events — so it reads as signal when it is noise. Omitted\n // rather than emitted as a near-constant.\n const headlessMeaningful =\n classification.kind === 'headless-likely' || classification.kind === 'browser'\n\n const distinctId = await hashId(`${ip}:${userAgent}`, resolveSecret(opts.idSecret))\n\n const event = {\n event: opts.eventName ?? 'agent_visit',\n distinctId,\n timestamp: new Date().toISOString(),\n properties: {\n // Caller properties are spread FIRST so library-computed fields always\n // win. Spreading them last let a colliding key silently overwrite\n // bot_name or is_ai_bot — corrupting the very classification they were\n // meant to annotate.\n ...opts.properties,\n $process_person_profile: false,\n $current_url: origin ? `${origin}${pathname}` : pathname,\n path: pathname,\n method: req.method,\n ...(opts.captureCountry ? { country_code: country } : {}),\n ...(geo ?? {}),\n ...(opts.captureIp ? { client_ip: ip || null } : {}),\n user_agent: userAgent,\n is_ai_bot: classification.isAiBot,\n bot_name: classification.label,\n ua_category: classification.kind,\n coding_agent_hint: classification.codingAgentHint,\n ...(headlessMeaningful\n ? {\n headless_score: classification.headless?.score ?? 0,\n headless_likely: classification.headless?.likely ?? false\n }\n : {}),\n ...(verification\n ? {\n bot_verified: verification.verified,\n bot_verification: verification.verdict,\n ...(verification.reason ? { bot_verification_reason: verification.reason } : {})\n }\n : {}),\n referer,\n source: opts.source ?? null\n }\n }\n\n await opts.analytics.capture(event)\n } catch (err) {\n // Analytics must never affect the response — but silence is how a wrong API\n // key goes unnoticed for a week, so surface it when the caller asks.\n opts.onError?.(err instanceof Error ? err : new Error(String(err)))\n }\n}\n\n// Vercel edge URL-encodes city/region (e.g. `San%20Francisco`); decode so\n// downstream consumers don't have to. Numeric fields (lat/lng) and timezone\n// pass through untouched. Headers without a value are dropped rather than\n// emitted as empty strings.\nfunction extractGeo(req: Request): Record<string, string> {\n const decode = (v: string | null) => {\n if (!v) return ''\n try {\n return decodeURIComponent(v)\n } catch {\n return v\n }\n }\n const fields: Array<[string, string]> = [\n ['region', decode(req.headers.get('x-vercel-ip-country-region'))],\n ['city', decode(req.headers.get('x-vercel-ip-city'))],\n ['latitude', req.headers.get('x-vercel-ip-latitude') ?? ''],\n ['longitude', req.headers.get('x-vercel-ip-longitude') ?? ''],\n ['timezone', req.headers.get('x-vercel-ip-timezone') ?? '']\n ]\n const out: Record<string, string> = {}\n for (const [k, v] of fields) if (v) out[k] = v\n return out\n}\n","/** Thrown when the analytics backend rejects, errors, or times out a capture. */\nexport class CaptureTransportError extends Error {\n readonly status: number | undefined\n readonly body: string | undefined\n constructor(message: string, status?: number, body?: string) {\n super(message)\n this.name = 'CaptureTransportError'\n this.status = status\n this.body = body\n }\n}\n","import type { AnalyticsAdapter, CaptureEvent } from '../types.js'\nimport { CaptureTransportError } from '../errors.js'\n\n\nexport interface PostHogAdapterConfig {\n /** PostHog project API key (the public one used by the JS SDK). */\n apiKey: string\n /**\n * PostHog host, with or without scheme. Defaults to `https://us.i.posthog.com`.\n * Use `https://eu.i.posthog.com` for EU cloud, or your own reverse-proxy\n * domain (e.g. `https://svc.example.com`).\n */\n host?: string\n /**\n * Path on the host that accepts single-event captures. Defaults to\n * `/i/v0/e/` which is PostHog's current endpoint for this.\n */\n path?: string\n /**\n * Override the `fetch` implementation (useful for tests or custom runtimes\n * that need a pinned fetch).\n */\n fetchImpl?: typeof fetch\n /**\n * Abort the capture after this many milliseconds. Defaults to 3000. Without\n * a bound, a hung backend leaves a pending promise for the lifetime of an\n * edge invocation.\n */\n timeoutMs?: number\n}\n\n/**\n * Adapter that posts each event to the PostHog capture endpoint. Uses\n * `keepalive: true` so the request survives after a serverless response\n * returns — events aren't guaranteed (fire-and-forget), but that's the\n * trade we want to keep the hot path fast.\n */\nexport function posthogAnalytics(config: PostHogAdapterConfig): AnalyticsAdapter {\n const hostRaw = config.host ?? 'https://us.i.posthog.com'\n const base = (/^https?:\\/\\//.test(hostRaw) ? hostRaw : `https://${hostRaw}`).replace(/\\/$/, '')\n const path = (config.path ?? '/i/v0/e/').replace(/^(?!\\/)/, '/')\n const endpoint = `${base}${path}`\n const fetchImpl = config.fetchImpl ?? fetch\n\n return {\n async capture(event: CaptureEvent): Promise<void> {\n const payload = {\n api_key: config.apiKey,\n event: event.event,\n distinct_id: event.distinctId,\n timestamp: event.timestamp,\n properties: event.properties\n }\n // A 401 from a mistyped key used to look identical to success. Surface\n // it: `trackVisit` routes it to `onError` and still never throws into\n // the response path.\n const res = await fetchImpl(endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n keepalive: true,\n signal: AbortSignal.timeout(config.timeoutMs ?? 3000)\n })\n if (!res.ok) {\n throw new CaptureTransportError(\n `PostHog capture failed: ${res.status} ${res.statusText}`,\n res.status,\n await res.text().catch(() => undefined)\n )\n }\n }\n }\n}\n","import type { AnalyticsAdapter, CaptureEvent } from '../types.js'\nimport { CaptureTransportError } from '../errors.js'\n\nexport interface WebhookAdapterConfig {\n /** Destination URL that receives a POST for each event. */\n url: string\n /** Extra headers merged onto the POST (useful for shared-secret auth). */\n headers?: Record<string, string>\n /**\n * Transform the event into the exact JSON body the destination expects.\n * Defaults to sending the {@link CaptureEvent} as-is.\n */\n transform?: (event: CaptureEvent) => unknown\n /** Override the `fetch` implementation. */\n fetchImpl?: typeof fetch\n /** Abort the capture after this many milliseconds. Defaults to 3000. */\n timeoutMs?: number\n}\n\n/**\n * Adapter that POSTs each event to an arbitrary webhook URL. Keeps the\n * library analytics-backend-agnostic — use this when PostHog isn't your\n * analytics of record, or when you want to multiplex events through your\n * own ingestion layer.\n */\nexport function webhookAnalytics(config: WebhookAdapterConfig): AnalyticsAdapter {\n const fetchImpl = config.fetchImpl ?? fetch\n const transform = config.transform ?? ((e: CaptureEvent): unknown => e)\n\n return {\n async capture(event: CaptureEvent): Promise<void> {\n const res = await fetchImpl(config.url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(config.headers ?? {})\n },\n body: JSON.stringify(transform(event)),\n keepalive: true,\n signal: AbortSignal.timeout(config.timeoutMs ?? 3000)\n })\n if (!res.ok) {\n throw new CaptureTransportError(\n `Webhook capture failed: ${res.status} ${res.statusText}`,\n res.status,\n await res.text().catch(() => undefined)\n )\n }\n }\n }\n}\n","import type { AnalyticsAdapter, CaptureEvent } from '../types.js'\n\n/**\n * Escape hatch for wiring a callback directly as an analytics adapter.\n * Useful when you want to log events, pipe them through your own SDK, or\n * compose multiple adapters.\n *\n * @example\n * ```ts\n * const devAnalytics = customAnalytics((e) => console.log('[doc_view]', e))\n * ```\n */\nexport function customAnalytics(\n capture: (event: CaptureEvent) => Promise<void> | void\n): AnalyticsAdapter {\n return { capture }\n}\n"]}
1
+ {"version":3,"sources":["../src/bots.ts","../src/hash.ts","../src/track.ts","../src/errors.ts","../src/policy.ts","../src/adapters/posthog.ts","../src/adapters/webhook.ts","../src/adapters/custom.ts"],"names":["AI_BOT_PATTERN","HTTP_CLIENT_PATTERN","isAiBot","userAgent","isHttpClient","parseBotName","s","firstUserAgentProduct","compatibleMatch","detectHeadless","req","signals","ua","secChUa","accept","score","classifyAgent","label","aiBot","httpClient","kind","classifyRequest","base","headless","HashSecretError","message","subtle","c","KEYS","keyFor","secret","k","hashId","input","sig","bytes","out","b","randomSecret","x","fallbackSecret","warnedNoSecret","resolveSecret","explicit","fromEnv","trackVisit","opts","onlyBots","skipBrowsers","pathname","originFromUrl","url","origin","ip","referer","country","geo","extractGeo","classification","verification","headlessMeaningful","distinctId","event","err","decode","v","fields","CaptureTransportError","status","body","RETRIEVAL","TRAINING","SEARCH","agentIntent","agentPolicy","intent","entry","posthogAnalytics","config","hostRaw","path","endpoint","fetchImpl","payload","res","webhookAnalytics","transform","e","customAnalytics","capture"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * User-agent substrings that identify **publicly declared** AI crawlers — the\n * branded bots that identify themselves by name (OpenAI's GPTBot, Anthropic's\n * ClaudeBot, Perplexity-User, Google-Extended, etc.). High-confidence: when\n * this matches, the request almost certainly comes from that vendor's crawler\n * fleet.\n *\n * Does NOT include **coding-agent traffic** (Claude Code, Cline, Cursor,\n * Windsurf, Aider, OpenCode, VS Code). Those tools use generic HTTP library\n * UAs (axios, curl, got, colly, Electron) or spoof full browser UAs — they\n * can't be distinguished from non-AI traffic by UA alone. See\n * {@link HTTP_CLIENT_PATTERN} for the loose heuristic layer.\n *\n * Sources consulted when updating: darkvisitors.com, vendor docs from OpenAI,\n * Anthropic, Google, Perplexity, Cohere, Apple, Bytedance.\n */\nexport const AI_BOT_PATTERN =\n /ClaudeBot|Claude-User|Claude-SearchBot|Claude-Web|Anthropic|GPTBot|ChatGPT-User|OAI-SearchBot|PerplexityBot|Perplexity-User|Google-Extended|Google-CloudVertexBot|Google-Agent|GoogleAgent-Mariner|Gemini-Deep-Research|Applebot|cohere|Bytespider|CCBot|Amazonbot|Amzn-SearchBot|NovaAct|AzureAI-SearchBot|Meta-ExternalAgent|meta-externalfetcher|meta-webindexer|FacebookBot|DuckAssistBot|MistralAI-User|YouBot|AI2Bot|Diffbot|DeepSeek|PanguBot|Webzio-Extended|omgili|Timpibot|Grok|Manus-User|quillbot|MyCentralAIScraperBot|Cursor|Windsurf/i\n\n/**\n * HTTP library / runtime signatures frequently used by coding agents. Matching\n * any of these is a **loose** signal — legitimate curl scripts, CI jobs, and\n * server-to-server traffic use the same libraries. Use this for the wider\n * net (`coding_agent_hint: true`) and pair with other signals (request\n * shape, JA4 fingerprint, path patterns) for higher confidence.\n *\n * Based on behavioural signatures observed by Addy Osmani:\n * Claude Code → axios/1.8.4\n * Cline, Junie → curl/8.4.0\n * Cursor → got (sindresorhus/got)\n * Windsurf → colly\n * VS Code → Electron / Chromium\n *\n * Aider and OpenCode use Playwright-driven full Mozilla/Safari UAs and are\n * indistinguishable from real browsers at the UA layer.\n */\nexport const HTTP_CLIENT_PATTERN =\n /axios\\/|curl\\/|(?:^|[\\s(])got(?:\\/|[\\s(])|\\bcolly\\b|Electron\\/|node-fetch\\/|python-requests\\/|Go-http-client\\/|okhttp\\/|aiohttp\\/|Deno\\//i\n\nexport function isAiBot(userAgent: string | null | undefined): boolean {\n if (!userAgent) return false\n return AI_BOT_PATTERN.test(userAgent)\n}\n\nexport function isHttpClient(userAgent: string | null | undefined): boolean {\n if (!userAgent) return false\n return HTTP_CLIENT_PATTERN.test(userAgent)\n}\n\n/**\n * Map a user-agent string to a coarse, human-readable label. Returns one of:\n *\n * - A branded-crawler name (`'Claude'`, `'ChatGPT'`, …) — pair with\n * {@link isAiBot} for `is_ai_bot: true` segmentation.\n * - An HTTP-library name (`'curl'`, `'axios'`, `'got'`, `'colly'`,\n * `'Electron'`, …) — hint of a coding agent or automation; not\n * conclusive. Pair with {@link isHttpClient}.\n * - `'Browser'` for typical desktop browsers (possibly spoofed by\n * Playwright-based agents like Aider/OpenCode — this label alone can't\n * tell you).\n * - `'Other'` for anything unrecognised or empty input.\n */\nexport function parseBotName(userAgent: string | null | undefined): string {\n if (!userAgent || typeof userAgent !== 'string') return 'Other'\n const s = userAgent.toLowerCase()\n\n // Publicly declared AI crawlers (high confidence).\n if (s.includes('chatgpt-user') || s.includes('gptbot') || s.includes('oai-searchbot') || s.includes('openai'))\n return 'ChatGPT'\n if (\n s.includes('claudebot') ||\n s.includes('claude-user') ||\n s.includes('claude-searchbot') ||\n s.includes('claude-web') ||\n s.includes('anthropic')\n )\n return 'Claude'\n if (s.includes('perplexitybot') || s.includes('perplexity-user')) return 'Perplexity'\n if (s.includes('ccbot')) return 'Common Crawl'\n if (\n s.includes('google-extended') ||\n s.includes('googlebot') ||\n s.includes('google-cloudvertexbot') ||\n s.includes('google-agent') ||\n s.includes('googleagent-mariner') ||\n s.includes('gemini-deep-research')\n )\n return 'Google'\n if (s.includes('applebot')) return 'Apple'\n if (s.includes('bingbot')) return 'Bing'\n if (s.includes('bytespider')) return 'Bytespider'\n if (s.includes('amazonbot') || s.includes('amzn-searchbot') || s.includes('novaact')) return 'Amazon'\n if (\n s.includes('meta-externalagent') ||\n s.includes('meta-externalfetcher') ||\n s.includes('meta-webindexer') ||\n s.includes('facebookbot')\n )\n return 'Meta'\n if (s.includes('mistralai-user')) return 'Mistral'\n if (s.includes('duckassistbot')) return 'DuckDuckGo'\n if (s.includes('youbot')) return 'You.com'\n if (s.includes('diffbot')) return 'Diffbot'\n if (s.includes('ai2bot')) return 'AI2'\n if (s.includes('cohere')) return 'Cohere'\n if (s.includes('cursor')) return 'Cursor'\n if (s.includes('windsurf')) return 'Windsurf'\n if (s.includes('deepseek')) return 'DeepSeek'\n if (s.includes('pangubot')) return 'Huawei'\n if (s.includes('webzio') || s.includes('omgili')) return 'Webz.io'\n if (s.includes('timpibot')) return 'Timpi'\n if (s.includes('grok') || s.includes('xai-')) return 'xAI'\n if (s.includes('manus-user')) return 'Manus'\n if (s.includes('quillbot')) return 'QuillBot'\n if (s.includes('azureai-searchbot')) return 'Microsoft'\n if (s.includes('mycentralaiscraperbot')) return 'MyCentralAI'\n if (s.includes('petalbot')) return 'PetalBot'\n\n // SEO crawlers and monitoring bots.\n if (s.includes('ahrefsbot')) return 'Ahrefs'\n if (s.includes('semrushbot')) return 'Semrush'\n if (s.includes('mj12bot')) return 'Majestic'\n if (s.includes('dotbot')) return 'Moz'\n if (s.includes('rogerbot')) return 'Moz'\n if (s.includes('screaming frog')) return 'Screaming Frog'\n if (s.includes('sitebulb')) return 'Sitebulb'\n if (s.includes('linkfluence')) return 'Linkfluence'\n if (s.includes('dataforseo')) return 'DataForSEO'\n if (s.includes('serpstatbot')) return 'Serpstat'\n\n // Monitoring and feed bots.\n if (s.includes('uptimerobot')) return 'UptimeRobot'\n if (s.includes('pingdom')) return 'Pingdom'\n if (s.includes('statuscake')) return 'StatusCake'\n if (s.includes('newrelicpinger')) return 'New Relic'\n if (s.includes('datadogagent') || s.includes('datadog')) return 'Datadog'\n if (s.includes('slackbot')) return 'Slack'\n if (s.includes('twitterbot')) return 'Twitter'\n if (s.includes('linkedinbot')) return 'LinkedIn'\n if (s.includes('discordbot')) return 'Discord'\n if (s.includes('telegrambot')) return 'Telegram'\n if (s.includes('whatsapp')) return 'WhatsApp'\n\n // AI search and indexing bots.\n if (s.includes('linkupbot')) return 'Linkup'\n if (s.includes('sogou')) return 'Sogou'\n if (s.includes('yandexbot')) return 'Yandex'\n if (s.includes('baiduspider')) return 'Baidu'\n\n // Link preview fetchers.\n if (s.includes('facebookexternalhit')) return 'Facebook'\n if (s.includes('com.apple.webkit')) return 'Apple URL Preview'\n\n // Uptime and monitoring.\n if (s.includes('ohdear')) return 'Oh Dear'\n\n // Generic scrapers.\n if (s.includes('scrapy')) return 'Scrapy'\n if (s.includes('headlesschrome')) return 'Headless Chrome'\n if (s.includes('phantomjs')) return 'PhantomJS'\n if (s.includes('wget')) return 'wget'\n if (s.includes('httpie')) return 'HTTPie'\n if (s.includes('guzzlehttp')) return 'Guzzle'\n\n // HTTP library / runtime signatures (loose — coding agent or automation).\n // Check Electron before Browser since Electron UAs contain Chrome/Safari.\n if (s.includes('electron/')) return 'Electron'\n if (/curl\\//.test(s)) return 'curl'\n if (/axios\\//.test(s)) return 'axios'\n if (/(?:^|[\\s(])got(?:\\/|[\\s(])/.test(s)) return 'got'\n if (/\\bcolly\\b/.test(s)) return 'colly'\n if (/node-fetch\\//.test(s)) return 'node-fetch'\n if (/python-requests\\//.test(s)) return 'python-requests'\n if (/go-http-client\\//.test(s)) return 'Go http client'\n if (/okhttp\\//.test(s)) return 'OkHttp'\n if (/aiohttp\\//.test(s)) return 'aiohttp'\n if (/deno\\//.test(s)) return 'Deno'\n\n // Real browsers (or UAs spoofed to look like them — see Aider/OpenCode note).\n if (s.includes('mozilla') || s.includes('chrome') || s.includes('safari') || s.includes('firefox'))\n return 'Browser'\n\n return 'Other'\n}\n\n/**\n * Return the first product token from a UA header, useful for segmenting by\n * client without hard-coding every bot name. Falls back to `'Other'` for empty\n * input.\n */\nexport function firstUserAgentProduct(userAgent: string | null | undefined): string {\n if (!userAgent || typeof userAgent !== 'string') return 'Other'\n const compatibleMatch = userAgent.match(/compatible;\\s*([^/;\\s]+)(?:\\/[^\\s;]*)?/i)\n if (compatibleMatch && compatibleMatch[1]) return compatibleMatch[1].trim()\n const first = userAgent.trim().split('/')[0]?.trim().split(/\\s+/)[0]?.trim()\n return first || 'Other'\n}\n\n/**\n * Detect likely headless/automated browsers by checking for missing headers\n * that real browsers always send. Playwright, Puppeteer, and similar tools\n * spoof the UA but often omit standard browser headers.\n *\n * Signals checked (each scores 1 point):\n * - Missing `Accept-Language` — every real browser sends this\n * - Missing `Sec-Fetch-Mode` — sent by all modern browsers\n * - Missing `Sec-CH-UA` — Client Hints, Chromium 89+\n * - `Sec-CH-UA` contains \"HeadlessChrome\"\n * - Missing or bare Accept header — browsers send detailed accept lists\n * - `Connection: close` with browser UA — browsers use keep-alive\n *\n * Returns a score (0-6), the signals that fired, and a boolean `likely`\n * flag (score >= 2 with a browser-like UA).\n */\nexport function detectHeadless(req: Request): HeadlessDetection {\n const signals: string[] = []\n const ua = (req.headers.get('user-agent') || '').toLowerCase()\n const isBrowserUA =\n ua.includes('mozilla') || ua.includes('chrome') || ua.includes('safari') || ua.includes('firefox')\n\n if (!isBrowserUA) return { score: 0, signals: [], likely: false }\n\n if (!req.headers.get('accept-language')) {\n signals.push('missing-accept-language')\n }\n if (!req.headers.get('sec-fetch-mode')) {\n signals.push('missing-sec-fetch-mode')\n }\n const secChUa = req.headers.get('sec-ch-ua')\n if (!secChUa) {\n signals.push('missing-sec-ch-ua')\n } else if (secChUa.toLowerCase().includes('headlesschrome')) {\n signals.push('headless-chrome-hint')\n }\n const accept = req.headers.get('accept') || ''\n if (!accept || accept === '*/*') {\n signals.push('missing-or-bare-accept')\n }\n if ((req.headers.get('connection') || '').toLowerCase() === 'close') {\n signals.push('connection-close')\n }\n\n const score = signals.length\n return { score, signals, likely: score >= 2 }\n}\n\nexport interface HeadlessDetection {\n /** Number of suspicious signals found (0-6). */\n score: number\n /** Names of the specific signals that fired. */\n signals: string[]\n /** True when score >= 2 — strong headless indication. */\n likely: boolean\n}\n\nexport type AgentKind =\n | 'declared-crawler'\n | 'coding-agent-hint'\n | 'headless-likely'\n | 'browser'\n | 'other'\n\nexport interface AgentClassification {\n /**\n * Categorical tag for the request:\n *\n * - `'declared-crawler'` — {@link AI_BOT_PATTERN} matched. High confidence.\n * - `'coding-agent-hint'` — {@link HTTP_CLIENT_PATTERN} matched. Loose\n * signal; could be a coding agent, a curl script, or any automation.\n * - `'headless-likely'` — Browser-like UA but missing standard headers.\n * Strong signal of Playwright/Puppeteer automation (Aider, OpenCode, etc.).\n * - `'browser'` — Looks like a real browser with expected headers present.\n * - `'other'` — Unrecognised or empty.\n */\n kind: AgentKind\n /** Human-readable label, same string {@link parseBotName} returns. */\n label: string\n /** Strict: `true` only when the UA matches a branded AI crawler. */\n isAiBot: boolean\n /** Loose: `true` for known HTTP-library / automation UAs. */\n codingAgentHint: boolean\n /** Headless browser detection result. Only populated when `req` is passed. */\n headless?: HeadlessDetection\n}\n\n/**\n * UA-only classification. Use {@link classifyRequest} for full detection\n * including headless browser heuristics.\n */\nexport function classifyAgent(userAgent: string | null | undefined): AgentClassification {\n const label = parseBotName(userAgent)\n const aiBot = isAiBot(userAgent)\n const httpClient = isHttpClient(userAgent)\n\n let kind: AgentKind\n if (aiBot) kind = 'declared-crawler'\n else if (httpClient) kind = 'coding-agent-hint'\n else if (label === 'Browser') kind = 'browser'\n else kind = 'other'\n\n return { kind, label, isAiBot: aiBot, codingAgentHint: httpClient }\n}\n\n/**\n * Full request classification — combines UA parsing with header-based\n * headless detection. When a browser-like UA is missing standard headers,\n * the kind is promoted from `'browser'` to `'headless-likely'`.\n */\nexport function classifyRequest(req: Request): AgentClassification {\n const userAgent = req.headers.get('user-agent') || ''\n const base = classifyAgent(userAgent)\n const headless = detectHeadless(req)\n\n let kind = base.kind\n let label = base.label\n if (kind === 'browser' && headless.likely) {\n kind = 'headless-likely'\n // Relabel too. Leaving it as 'Browser' meant automation with a spoofed\n // browser UA — 79% of one production site's agent traffic — was\n // indistinguishable from a human in any `bot_name` breakdown, and was\n // silently excluded by the obvious `bot_name != 'Browser'` filter.\n label = 'Headless'\n }\n\n return { ...base, kind, label, headless }\n}\n","/**\n * Keyed, non-reversible anonymous identifiers.\n *\n * The previous implementation was an unsalted 32-bit djb2 over `ip:userAgent`.\n * Because the user agent is emitted in plaintext on the same event, an attacker\n * held half the preimage and only had to search the IPv4 space — recovering a\n * residential IP took 75 seconds single-threaded. That is pseudonymisation, not\n * anonymisation, and it does not survive GDPR Recital 26.\n *\n * This uses HMAC-SHA-256 with a caller-supplied secret, truncated to 64 bits.\n * Web Crypto is available on Vercel Edge, Cloudflare Workers, Deno and Node 18+.\n */\n\n/** Thrown when a secret is missing or unusable. */\nexport class HashSecretError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'HashSecretError'\n }\n}\n\n/**\n * Web Crypto, or a clear error explaining why it is missing.\n *\n * `globalThis.crypto` is present by default from Node 19; on Node 18 it sat\n * behind `--experimental-global-webcrypto`. We require Node >= 20 rather than\n * shipping a `node:crypto` fallback, because a static import of a Node builtin\n * breaks bundling for the edge runtimes this library primarily targets — and\n * Node 18 reached end of life in April 2025.\n */\nfunction subtle(): SubtleCrypto {\n const c = globalThis.crypto\n if (!c?.subtle) {\n throw new HashSecretError(\n 'Web Crypto is unavailable. agent-analytics requires Node >= 20, or any ' +\n 'runtime exposing globalThis.crypto.subtle (Vercel Edge, Cloudflare ' +\n 'Workers, Deno, browsers).'\n )\n }\n return c.subtle\n}\n\n// Importing a CryptoKey costs more than the signature itself, so keep one per\n// secret. Bounded by however many secrets a process configures — realistically\n// one.\nconst KEYS = new Map<string, Promise<CryptoKey>>()\n\nfunction keyFor(secret: string): Promise<CryptoKey> {\n let k = KEYS.get(secret)\n if (!k) {\n k = subtle().importKey(\n 'raw',\n new TextEncoder().encode(secret),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign']\n )\n KEYS.set(secret, k)\n }\n return k\n}\n\n/**\n * Hash `input` under `secret`, returning `anon_` followed by 16 hex characters\n * (64 bits — collision-free well past any realistic distinct-visitor count).\n *\n * The secret must be stable across instances for identifiers to be comparable\n * over time, and secret from anyone who can read your events: publishing it\n * makes the identifier exactly as reversible as the old implementation was.\n * Rotating it deliberately breaks continuity, which is correct behaviour for a\n * privacy-preserving id.\n */\nexport async function hashId(input: string, secret: string): Promise<string> {\n if (typeof secret !== 'string' || secret.length === 0) {\n throw new HashSecretError('hashId requires a non-empty secret')\n }\n const sig = await subtle().sign('HMAC', await keyFor(secret), new TextEncoder().encode(input))\n const bytes = new Uint8Array(sig, 0, 8)\n let out = ''\n for (const b of bytes) out += b.toString(16).padStart(2, '0')\n return 'anon_' + out\n}\n\n/**\n * Generate a random secret. Used as the default when none is configured, so the\n * privacy-preserving path is the one you get by doing nothing. Identifiers are\n * then only stable within a single instance's lifetime — set a real secret when\n * you need them comparable across instances and over time.\n */\nexport function randomSecret(): string {\n const b = new Uint8Array(32)\n subtle() // surface the same clear error if Web Crypto is missing\n globalThis.crypto.getRandomValues(b)\n let out = ''\n for (const x of b) out += x.toString(16).padStart(2, '0')\n return out\n}\n","import { classifyRequest, detectHeadless, isAiBot, isHttpClient } from './bots.js'\nimport { hashId, randomSecret } from './hash.js'\nimport type { TrackVisitOptions } from './types.js'\n\n/**\n * Fallback secret, generated once per instance. Keeps the default path\n * privacy-preserving rather than making callers opt in to safety, at the cost\n * of identifiers that only correlate within one instance's lifetime.\n */\nlet fallbackSecret: string | undefined\nlet warnedNoSecret = false\n\nfunction resolveSecret(explicit: string | undefined): string {\n if (explicit) return explicit\n const fromEnv =\n typeof process !== 'undefined' ? process.env?.AGENT_ANALYTICS_ID_SECRET : undefined\n if (fromEnv) return fromEnv\n if (!fallbackSecret) {\n fallbackSecret = randomSecret()\n if (!warnedNoSecret) {\n warnedNoSecret = true\n // Once per instance, not once per request.\n console.warn(\n '[agent-analytics] No idSecret or AGENT_ANALYTICS_ID_SECRET set. ' +\n 'Using a per-instance random secret: distinctIds will not correlate ' +\n 'across instances or deploys.'\n )\n }\n }\n return fallbackSecret\n}\n\n/**\n * Capture an event describing the incoming request. Fire-and-forget: awaits the\n * adapter but routes errors to {@link TrackVisitOptions.onError} rather than\n * letting them reach the response path. Callers typically don't await it.\n *\n * By default, captures every request so coding-agent traffic (axios, curl,\n * Electron, …) shows up alongside branded crawlers. Set `onlyBots: true` to\n * restrict capture to UAs matching {@link AI_BOT_PATTERN}.\n */\nexport async function trackVisit(req: Request, opts: TrackVisitOptions): Promise<void> {\n const userAgent = req.headers.get('user-agent') || ''\n\n const onlyBots = opts.onlyBots ?? false\n const skipBrowsers = opts.skipBrowsers ?? false\n if (onlyBots && !isAiBot(userAgent)) return\n if (skipBrowsers && !isAiBot(userAgent) && !isHttpClient(userAgent)) {\n // Not a declared bot or HTTP client — check headless heuristics.\n // Playwright-based agents (Aider, OpenCode) will pass if they're missing\n // standard browser headers. Real browsers get skipped.\n if (!detectHeadless(req).likely) return\n }\n\n try {\n let pathname = '/'\n let originFromUrl = ''\n try {\n const url = new URL(req.url)\n pathname = url.pathname\n originFromUrl = url.origin\n } catch {\n // Some runtimes hand us a relative URL; fall back to the raw string.\n pathname = req.url || '/'\n }\n const origin = opts.origin ?? originFromUrl\n\n const forwardedFor = req.headers.get('x-forwarded-for') || ''\n const ip = forwardedFor.split(',')[0]?.trim() ?? ''\n const referer = req.headers.get('referer')\n const country = opts.captureCountry\n ? req.headers.get('x-vercel-ip-country') ||\n req.headers.get('cf-ipcountry') ||\n req.headers.get('x-country-code') ||\n null\n : null\n const geo = opts.captureGeo ? extractGeo(req) : null\n const classification = classifyRequest(req)\n\n // Verification is injected rather than imported, so the published IP range\n // tables only reach bundles that actually use them. Import `verifyRequest`\n // from `@apideck/agent-analytics/verify` and pass it as `verify`.\n // May be async: Web Bot Auth fetches a signer's key directory on first\n // sight of that origin, then serves from cache.\n const verification = opts.verify ? await opts.verify(req) : null\n\n // Headless scoring only discriminates for browser-shaped UAs. On a declared\n // crawler or an HTTP client it fires on nearly everything — measured true on\n // 99% of captured events — so it reads as signal when it is noise. Omitted\n // rather than emitted as a near-constant.\n const headlessMeaningful =\n classification.kind === 'headless-likely' || classification.kind === 'browser'\n\n const distinctId = await hashId(`${ip}:${userAgent}`, resolveSecret(opts.idSecret))\n\n const event = {\n event: opts.eventName ?? 'agent_visit',\n distinctId,\n timestamp: new Date().toISOString(),\n properties: {\n // Caller properties are spread FIRST so library-computed fields always\n // win. Spreading them last let a colliding key silently overwrite\n // bot_name or is_ai_bot — corrupting the very classification they were\n // meant to annotate.\n ...opts.properties,\n $process_person_profile: false,\n $current_url: origin ? `${origin}${pathname}` : pathname,\n path: pathname,\n method: req.method,\n ...(opts.captureCountry ? { country_code: country } : {}),\n ...(geo ?? {}),\n ...(opts.captureIp ? { client_ip: ip || null } : {}),\n user_agent: userAgent,\n is_ai_bot: classification.isAiBot,\n bot_name: classification.label,\n ua_category: classification.kind,\n coding_agent_hint: classification.codingAgentHint,\n ...(headlessMeaningful\n ? {\n headless_score: classification.headless?.score ?? 0,\n headless_likely: classification.headless?.likely ?? false\n }\n : {}),\n ...(verification\n ? {\n bot_verified: verification.verified,\n bot_verification: verification.verdict,\n ...(verification.reason ? { bot_verification_reason: verification.reason } : {})\n }\n : {}),\n referer,\n source: opts.source ?? null\n }\n }\n\n await opts.analytics.capture(event)\n } catch (err) {\n // Analytics must never affect the response — but silence is how a wrong API\n // key goes unnoticed for a week, so surface it when the caller asks.\n opts.onError?.(err instanceof Error ? err : new Error(String(err)))\n }\n}\n\n// Vercel edge URL-encodes city/region (e.g. `San%20Francisco`); decode so\n// downstream consumers don't have to. Numeric fields (lat/lng) and timezone\n// pass through untouched. Headers without a value are dropped rather than\n// emitted as empty strings.\nfunction extractGeo(req: Request): Record<string, string> {\n const decode = (v: string | null) => {\n if (!v) return ''\n try {\n return decodeURIComponent(v)\n } catch {\n return v\n }\n }\n const fields: Array<[string, string]> = [\n ['region', decode(req.headers.get('x-vercel-ip-country-region'))],\n ['city', decode(req.headers.get('x-vercel-ip-city'))],\n ['latitude', req.headers.get('x-vercel-ip-latitude') ?? ''],\n ['longitude', req.headers.get('x-vercel-ip-longitude') ?? ''],\n ['timezone', req.headers.get('x-vercel-ip-timezone') ?? '']\n ]\n const out: Record<string, string> = {}\n for (const [k, v] of fields) if (v) out[k] = v\n return out\n}\n","/** Thrown when the analytics backend rejects, errors, or times out a capture. */\nexport class CaptureTransportError extends Error {\n readonly status: number | undefined\n readonly body: string | undefined\n constructor(message: string, status?: number, body?: string) {\n super(message)\n this.name = 'CaptureTransportError'\n this.status = status\n this.body = body\n }\n}\n","import { classifyRequest } from './bots.js'\nimport type { BotVerificationLike } from './types.js'\n\n/**\n * What to do with an agent request.\n *\n * - `'allow'` — serve it, free. Humans, search crawlers, and the retrieval\n * agents you *want* reading your site.\n * - `'meter'` — serve it, but count it as billable. Bulk corpus collection.\n * - `'charge'` — don't serve it until it pays (HTTP 402).\n * - `'block'` — refuse. Failed identity verification, mostly.\n */\nexport type AgentAction = 'allow' | 'meter' | 'charge' | 'block'\n\n/**\n * Why an agent fetched the page. This is the distinction the whole module\n * exists for, and no other signal on the request carries it.\n *\n * - `'retrieval'` — a person asked a question and the assistant went to read\n * the page for them. This is *demand*: the agent is a distribution channel,\n * and charging for it is charging for your own marketing.\n * - `'training'` — bulk corpus collection for model training. You get nothing\n * back per fetch, which is where a price makes sense.\n * - `'search'` — classic index crawlers. Blocking these costs you SEO.\n * - `'tooling'` — coding agents and HTTP clients. Usually developers using\n * your docs; treat like retrieval unless you see abuse.\n * - `'unknown'` — everything else, including real browsers.\n */\nexport type AgentIntent = 'retrieval' | 'training' | 'search' | 'tooling' | 'unknown'\n\n/**\n * User agents where a human is waiting on the answer. Deliberately explicit\n * rather than pattern-guessed: `-User` is not a reliable marker (OpenAI's\n * ChatGPT-User fetches server-side; Claude Code's Claude-User runs on a\n * laptop), and getting this wrong means charging your own demand channel.\n */\nconst RETRIEVAL = /ChatGPT-User|OAI-SearchBot|Claude-User|Claude-SearchBot|Perplexity-User|claude-code|DuckAssistBot|MistralAI-User|Gemini-Deep-Research|Manus-User|YouBot/i\n\n/** Bulk crawlers that collect corpora. No human is waiting on these. */\nconst TRAINING = /GPTBot|ClaudeBot|Claude-Web|CCBot|Bytespider|Amazonbot|Amzn-SearchBot|Meta-ExternalAgent|meta-externalfetcher|meta-webindexer|FacebookBot|Google-Extended|Applebot-Extended|AI2Bot|Diffbot|omgili|Webzio-Extended|Timpibot|PanguBot|cohere|DeepSeek|Grok|quillbot|MyCentralAIScraperBot|NovaAct|AzureAI-SearchBot|Google-CloudVertexBot/i\n\n/** Classic search indexers — blocking these costs you organic traffic. */\nconst SEARCH = /bingbot|Googlebot|DuckDuckBot|YandexBot|Baiduspider|PetalBot|Sogou|Applebot(?!-Extended)/i\n\nexport interface AgentDecision {\n action: AgentAction\n intent: AgentIntent\n /** Vendor label, same string `parseBotName` returns. */\n label: string\n /** Identity verdict, when a verifier was supplied. */\n verification?: string\n /** Short human-readable justification — log it, don't parse it. */\n reason: string\n}\n\nexport interface AgentPolicyOptions {\n /**\n * Identity verifier. Import `verifyRequest` from\n * `@apideck/agent-analytics/verify` and pass it here to have a `spoofed`\n * verdict produce `'block'`.\n *\n * Injected rather than imported so the published IP range tables only reach\n * bundles that use them. Only meaningful when your edge controls\n * `x-forwarded-for`: behind a proxy that forwards a client-supplied header,\n * an attacker picks their own verdict.\n */\n verify?: (req: Request) => BotVerificationLike\n /** What to do with bulk training crawlers. Defaults to `'meter'`. */\n onTraining?: AgentAction\n /** What to do with retrieval agents. Defaults to `'allow'` — see AgentIntent. */\n onRetrieval?: AgentAction\n /** What to do with search indexers. Defaults to `'allow'`. */\n onSearch?: AgentAction\n /** What to do with coding agents and HTTP clients. Defaults to `'allow'`. */\n onTooling?: AgentAction\n /** Vendor labels or UA substrings always allowed, whatever the intent. */\n allowList?: readonly string[]\n}\n\n/** Classify why an agent is here, from its user agent alone. */\nexport function agentIntent(userAgent: string | null | undefined): AgentIntent {\n const ua = userAgent ?? ''\n if (!ua) return 'unknown'\n // Retrieval is checked first: several vendors ship both a bulk crawler and a\n // user-facing fetcher whose tokens overlap (ClaudeBot vs Claude-User).\n if (RETRIEVAL.test(ua)) return 'retrieval'\n if (TRAINING.test(ua)) return 'training'\n if (SEARCH.test(ua)) return 'search'\n return 'unknown'\n}\n\n/**\n * Decide what to do with a request. Pure classification plus policy — no\n * payment rails, no network calls, nothing to configure beyond the four\n * intent knobs.\n *\n * @example\n * ```ts\n * const decision = agentPolicy(req, { verify: true, onTraining: 'charge' })\n * if (decision.action === 'block') return new Response(null, { status: 403 })\n * if (decision.action === 'charge') return paymentRequired(decision)\n * ```\n */\nexport function agentPolicy(req: Request, opts: AgentPolicyOptions = {}): AgentDecision {\n const ua = req.headers.get('user-agent') || ''\n const classification = classifyRequest(req)\n const label = classification.label\n\n let intent = agentIntent(ua)\n // An HTTP-library UA that matched no vendor is a coding agent or a script.\n if (intent === 'unknown' && classification.codingAgentHint) intent = 'tooling'\n\n const allowed = opts.allowList?.some(\n (entry) => entry === label || ua.toLowerCase().includes(entry.toLowerCase())\n )\n if (allowed) {\n return { action: 'allow', intent, label, reason: 'on allowList' }\n }\n\n let verification: string | undefined\n if (opts.verify) {\n verification = opts.verify(req).verdict\n // Only 'spoofed' is actionable. 'unverifiable' means we couldn't check —\n // blocking on it would refuse every vendor without a published feed and\n // every coding agent running on someone's own machine.\n if (verification === 'spoofed') {\n return {\n action: 'block',\n intent,\n label,\n verification,\n reason: `${label} claimed but client IP is outside its published ranges`\n }\n }\n }\n\n const action: AgentAction =\n intent === 'training'\n ? (opts.onTraining ?? 'meter')\n : intent === 'retrieval'\n ? (opts.onRetrieval ?? 'allow')\n : intent === 'search'\n ? (opts.onSearch ?? 'allow')\n : intent === 'tooling'\n ? (opts.onTooling ?? 'allow')\n : 'allow'\n\n const REASONS: Record<AgentIntent, string> = {\n retrieval: 'a person is waiting on this answer',\n training: 'bulk corpus collection',\n search: 'search index crawler',\n tooling: 'coding agent or HTTP client',\n unknown: 'not a recognised agent'\n }\n\n return {\n action,\n intent,\n label,\n // Spread rather than assign: `exactOptionalPropertyTypes` distinguishes an\n // absent key from one explicitly set to undefined.\n ...(verification ? { verification } : {}),\n reason: REASONS[intent]\n }\n}\n","import type { AnalyticsAdapter, CaptureEvent } from '../types.js'\nimport { CaptureTransportError } from '../errors.js'\n\n\nexport interface PostHogAdapterConfig {\n /** PostHog project API key (the public one used by the JS SDK). */\n apiKey: string\n /**\n * PostHog host, with or without scheme. Defaults to `https://us.i.posthog.com`.\n * Use `https://eu.i.posthog.com` for EU cloud, or your own reverse-proxy\n * domain (e.g. `https://svc.example.com`).\n */\n host?: string\n /**\n * Path on the host that accepts single-event captures. Defaults to\n * `/i/v0/e/` which is PostHog's current endpoint for this.\n */\n path?: string\n /**\n * Override the `fetch` implementation (useful for tests or custom runtimes\n * that need a pinned fetch).\n */\n fetchImpl?: typeof fetch\n /**\n * Abort the capture after this many milliseconds. Defaults to 3000. Without\n * a bound, a hung backend leaves a pending promise for the lifetime of an\n * edge invocation.\n */\n timeoutMs?: number\n}\n\n/**\n * Adapter that posts each event to the PostHog capture endpoint. Uses\n * `keepalive: true` so the request survives after a serverless response\n * returns — events aren't guaranteed (fire-and-forget), but that's the\n * trade we want to keep the hot path fast.\n */\nexport function posthogAnalytics(config: PostHogAdapterConfig): AnalyticsAdapter {\n const hostRaw = config.host ?? 'https://us.i.posthog.com'\n const base = (/^https?:\\/\\//.test(hostRaw) ? hostRaw : `https://${hostRaw}`).replace(/\\/$/, '')\n const path = (config.path ?? '/i/v0/e/').replace(/^(?!\\/)/, '/')\n const endpoint = `${base}${path}`\n const fetchImpl = config.fetchImpl ?? fetch\n\n return {\n async capture(event: CaptureEvent): Promise<void> {\n const payload = {\n api_key: config.apiKey,\n event: event.event,\n distinct_id: event.distinctId,\n timestamp: event.timestamp,\n properties: event.properties\n }\n // A 401 from a mistyped key used to look identical to success. Surface\n // it: `trackVisit` routes it to `onError` and still never throws into\n // the response path.\n const res = await fetchImpl(endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n keepalive: true,\n signal: AbortSignal.timeout(config.timeoutMs ?? 3000)\n })\n if (!res.ok) {\n throw new CaptureTransportError(\n `PostHog capture failed: ${res.status} ${res.statusText}`,\n res.status,\n await res.text().catch(() => undefined)\n )\n }\n }\n }\n}\n","import type { AnalyticsAdapter, CaptureEvent } from '../types.js'\nimport { CaptureTransportError } from '../errors.js'\n\nexport interface WebhookAdapterConfig {\n /** Destination URL that receives a POST for each event. */\n url: string\n /** Extra headers merged onto the POST (useful for shared-secret auth). */\n headers?: Record<string, string>\n /**\n * Transform the event into the exact JSON body the destination expects.\n * Defaults to sending the {@link CaptureEvent} as-is.\n */\n transform?: (event: CaptureEvent) => unknown\n /** Override the `fetch` implementation. */\n fetchImpl?: typeof fetch\n /** Abort the capture after this many milliseconds. Defaults to 3000. */\n timeoutMs?: number\n}\n\n/**\n * Adapter that POSTs each event to an arbitrary webhook URL. Keeps the\n * library analytics-backend-agnostic — use this when PostHog isn't your\n * analytics of record, or when you want to multiplex events through your\n * own ingestion layer.\n */\nexport function webhookAnalytics(config: WebhookAdapterConfig): AnalyticsAdapter {\n const fetchImpl = config.fetchImpl ?? fetch\n const transform = config.transform ?? ((e: CaptureEvent): unknown => e)\n\n return {\n async capture(event: CaptureEvent): Promise<void> {\n const res = await fetchImpl(config.url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(config.headers ?? {})\n },\n body: JSON.stringify(transform(event)),\n keepalive: true,\n signal: AbortSignal.timeout(config.timeoutMs ?? 3000)\n })\n if (!res.ok) {\n throw new CaptureTransportError(\n `Webhook capture failed: ${res.status} ${res.statusText}`,\n res.status,\n await res.text().catch(() => undefined)\n )\n }\n }\n }\n}\n","import type { AnalyticsAdapter, CaptureEvent } from '../types.js'\n\n/**\n * Escape hatch for wiring a callback directly as an analytics adapter.\n * Useful when you want to log events, pipe them through your own SDK, or\n * compose multiple adapters.\n *\n * @example\n * ```ts\n * const devAnalytics = customAnalytics((e) => console.log('[doc_view]', e))\n * ```\n */\nexport function customAnalytics(\n capture: (event: CaptureEvent) => Promise<void> | void\n): AnalyticsAdapter {\n return { capture }\n}\n"]}
@@ -38,7 +38,7 @@ interface TrackVisitOptions {
38
38
  * Injected rather than imported so the published IP range tables — the
39
39
  * largest thing in the package — only reach bundles that use them.
40
40
  */
41
- verify?: (req: Request) => BotVerificationLike;
41
+ verify?: (req: Request) => BotVerificationLike | Promise<BotVerificationLike>;
42
42
  /**
43
43
  * Label describing how the request arrived (e.g. `'page-view'`, `'md-suffix'`,
44
44
  * `'ua-rewrite'`). Emitted as a `source` property on the captured event so
@@ -38,7 +38,7 @@ interface TrackVisitOptions {
38
38
  * Injected rather than imported so the published IP range tables — the
39
39
  * largest thing in the package — only reach bundles that use them.
40
40
  */
41
- verify?: (req: Request) => BotVerificationLike;
41
+ verify?: (req: Request) => BotVerificationLike | Promise<BotVerificationLike>;
42
42
  /**
43
43
  * Label describing how the request arrived (e.g. `'page-view'`, `'md-suffix'`,
44
44
  * `'ua-rewrite'`). Emitted as a `source` property on the captured event so
package/dist/verify.cjs CHANGED
@@ -1,3 +1,4 @@
1
- 'use strict';function b(t){if(!t||typeof t!="string")return "Other";let e=t.toLowerCase();return e.includes("chatgpt-user")||e.includes("gptbot")||e.includes("oai-searchbot")||e.includes("openai")?"ChatGPT":e.includes("claudebot")||e.includes("claude-user")||e.includes("claude-searchbot")||e.includes("claude-web")||e.includes("anthropic")?"Claude":e.includes("perplexitybot")||e.includes("perplexity-user")?"Perplexity":e.includes("ccbot")?"Common Crawl":e.includes("google-extended")||e.includes("googlebot")||e.includes("google-cloudvertexbot")||e.includes("google-agent")||e.includes("googleagent-mariner")||e.includes("gemini-deep-research")?"Google":e.includes("applebot")?"Apple":e.includes("bingbot")?"Bing":e.includes("bytespider")?"Bytespider":e.includes("amazonbot")||e.includes("amzn-searchbot")||e.includes("novaact")?"Amazon":e.includes("meta-externalagent")||e.includes("meta-externalfetcher")||e.includes("meta-webindexer")||e.includes("facebookbot")?"Meta":e.includes("mistralai-user")?"Mistral":e.includes("duckassistbot")?"DuckDuckGo":e.includes("youbot")?"You.com":e.includes("diffbot")?"Diffbot":e.includes("ai2bot")?"AI2":e.includes("cohere")?"Cohere":e.includes("cursor")?"Cursor":e.includes("windsurf")?"Windsurf":e.includes("deepseek")?"DeepSeek":e.includes("pangubot")?"Huawei":e.includes("webzio")||e.includes("omgili")?"Webz.io":e.includes("timpibot")?"Timpi":e.includes("grok")||e.includes("xai-")?"xAI":e.includes("manus-user")?"Manus":e.includes("quillbot")?"QuillBot":e.includes("azureai-searchbot")?"Microsoft":e.includes("mycentralaiscraperbot")?"MyCentralAI":e.includes("petalbot")?"PetalBot":e.includes("ahrefsbot")?"Ahrefs":e.includes("semrushbot")?"Semrush":e.includes("mj12bot")?"Majestic":e.includes("dotbot")||e.includes("rogerbot")?"Moz":e.includes("screaming frog")?"Screaming Frog":e.includes("sitebulb")?"Sitebulb":e.includes("linkfluence")?"Linkfluence":e.includes("dataforseo")?"DataForSEO":e.includes("serpstatbot")?"Serpstat":e.includes("uptimerobot")?"UptimeRobot":e.includes("pingdom")?"Pingdom":e.includes("statuscake")?"StatusCake":e.includes("newrelicpinger")?"New Relic":e.includes("datadogagent")||e.includes("datadog")?"Datadog":e.includes("slackbot")?"Slack":e.includes("twitterbot")?"Twitter":e.includes("linkedinbot")?"LinkedIn":e.includes("discordbot")?"Discord":e.includes("telegrambot")?"Telegram":e.includes("whatsapp")?"WhatsApp":e.includes("linkupbot")?"Linkup":e.includes("sogou")?"Sogou":e.includes("yandexbot")?"Yandex":e.includes("baiduspider")?"Baidu":e.includes("facebookexternalhit")?"Facebook":e.includes("com.apple.webkit")?"Apple URL Preview":e.includes("ohdear")?"Oh Dear":e.includes("scrapy")?"Scrapy":e.includes("headlesschrome")?"Headless Chrome":e.includes("phantomjs")?"PhantomJS":e.includes("wget")?"wget":e.includes("httpie")?"HTTPie":e.includes("guzzlehttp")?"Guzzle":e.includes("electron/")?"Electron":/curl\//.test(e)?"curl":/axios\//.test(e)?"axios":/(?:^|[\s(])got(?:\/|[\s(])/.test(e)?"got":/\bcolly\b/.test(e)?"colly":/node-fetch\//.test(e)?"node-fetch":/python-requests\//.test(e)?"python-requests":/go-http-client\//.test(e)?"Go http client":/okhttp\//.test(e)?"OkHttp":/aiohttp\//.test(e)?"aiohttp":/deno\//.test(e)?"Deno":e.includes("mozilla")||e.includes("chrome")||e.includes("safari")||e.includes("firefox")?"Browser":"Other"}var d={ChatGPT:["104.208.184.192/28","104.208.184.208/28","104.210.139.192/28","104.210.139.224/28","104.210.140.128/28","128.85.198.32/28","13.65.138.112/28","13.65.138.96/28","13.67.72.16/28","13.70.107.160/28","13.71.2.208/28","13.76.115.224/28","13.76.115.240/28","13.76.116.80/28","13.76.32.208/28","13.83.167.128/28","13.83.237.176/28","132.196.82.48/28","132.196.86.0/24","134.149.233.80/28","135.116.136.160/28","135.13.64.240/28","135.220.73.208/28","135.220.73.240/28","135.234.64.0/24","135.237.131.208/28","135.237.133.48/28","137.135.191.176/28","138.91.30.48/28","138.91.46.96/28","145.132.1.32/28","145.132.136.96/28","145.133.0.176/28","158.158.5.32/28","168.63.252.240/28","172.162.248.64/28","172.170.1.80/28","172.170.225.0/28","172.170.241.80/28","172.170.8.208/28","172.171.4.176/28","172.175.152.224/28","172.178.140.144/28","172.178.141.112/28","172.178.141.128/28","172.182.193.224/28","172.182.193.80/28","172.182.194.144/28","172.182.194.32/28","172.182.195.48/28","172.182.202.0/25","172.182.204.0/24","172.182.207.0/25","172.182.209.208/28","172.182.211.192/28","172.182.213.192/28","172.182.214.0/24","172.182.215.0/24","172.182.224.0/28","172.183.143.224/28","172.183.222.128/28","172.192.112.208/28","172.192.88.192/28","172.192.97.32/28","172.197.160.192/28","172.197.161.208/28","172.197.170.80/28","172.197.203.16/28","172.199.137.80/28","172.202.102.112/28","172.203.190.128/28","172.204.27.16/28","172.204.28.224/28","172.204.96.32/28","172.204.96.48/28","172.204.96.80/28","172.205.189.192/28","172.207.1.32/28","172.208.128.32/28","172.208.128.48/28","172.212.159.64/28","172.212.172.160/28","172.213.21.16/28","172.215.215.32/28","172.215.218.96/28","191.233.1.112/28","191.233.1.128/28","191.233.194.32/28","191.233.196.112/28","191.233.199.160/28","191.233.2.0/28","191.235.66.16/28","191.235.99.80/28","191.237.249.64/28","191.239.245.16/28","20.102.212.144/28","20.113.211.112/28","20.113.225.112/28","20.125.112.224/28","20.125.144.144/28","20.125.66.80/28","20.14.99.96/28","20.161.75.208/28","20.168.18.32/28","20.168.7.192/28","20.168.7.240/28","20.169.6.224/28","20.169.7.48/28","20.169.72.112/28","20.169.73.176/28","20.169.73.32/28","20.169.73.64/28","20.169.77.0/25","20.169.78.112/28","20.169.78.128/28","20.169.78.144/28","20.169.78.160/28","20.169.78.176/28","20.169.78.192/28","20.169.78.208/28","20.169.78.48/28","20.169.78.64/28","20.169.78.80/28","20.169.78.96/28","20.169.86.224/28","20.169.86.240/28","20.169.87.112/28","20.17.108.96/28","20.170.184.16/28","20.170.184.32/28","20.170.184.48/28","20.170.184.64/28","20.170.184.80/28","20.171.123.64/28","20.171.206.0/24","20.171.207.0/24","20.171.53.224/28","20.172.29.32/28","20.193.233.240/28","20.193.50.32/28","20.194.0.208/28","20.194.1.0/28","20.198.67.96/28","20.199.211.160/28","20.199.242.0/28","20.200.212.240/28","20.204.24.240/28","20.210.154.128/28","20.210.174.208/28","20.210.211.192/28","20.215.187.208/28","20.215.188.192/28","20.215.214.16/28","20.215.219.128/28","20.215.219.160/28","20.215.219.208/28","20.215.220.112/28","20.215.220.128/28","20.215.220.144/28","20.215.220.160/28","20.215.220.176/28","20.215.220.192/28","20.215.220.208/28","20.215.220.64/28","20.215.220.80/28","20.215.220.96/28","20.218.30.240/28","20.219.71.192/28","20.222.36.192/28","20.226.32.80/28","20.227.140.32/28","20.228.106.176/28","20.235.75.208/28","20.235.87.224/28","20.249.63.208/28","20.25.151.224/28","20.250.136.64/28","20.250.136.80/28","20.250.6.128/28","20.27.94.128/28","20.42.10.176/28","20.45.178.144/28","20.48.120.208/28","20.52.125.160/28","20.55.129.0/28","20.55.229.144/28","20.57.199.192/28","20.63.180.96/28","20.63.221.64/28","20.79.59.112/28","20.81.183.64/28","20.83.243.176/28","20.97.189.96/28","23.102.140.144/28","23.102.141.32/28","23.98.142.176/28","23.98.179.16/28","23.98.186.176/28","23.98.186.192/28","23.98.186.64/28","23.98.186.96/28","4.151.119.48/28","4.151.241.240/28","4.151.71.176/28","4.189.118.208/28","4.189.119.48/28","4.196.118.112/28","4.197.115.112/28","4.197.19.176/28","4.197.22.112/28","4.197.64.0/28","4.197.64.16/28","4.197.64.48/28","4.197.64.64/28","4.198.72.16/28","4.198.96.112/28","4.201.232.64/28","4.201.232.80/28","4.203.96.80/28","4.205.128.176/28","4.218.24.64/28","4.226.200.16/28","4.226.226.32/28","4.227.36.0/25","40.116.73.208/28","40.122.235.112/28","40.67.175.0/25","40.67.183.160/28","40.67.183.176/28","40.78.161.48/28","40.81.134.128/28","40.81.134.144/28","40.81.234.144/28","40.81.67.96/28","40.84.181.32/28","40.84.221.208/28","40.84.221.224/28","40.90.214.16/28","48.193.44.32/28","48.221.184.112/28","48.221.184.80/28","48.221.184.96/28","48.221.40.176/28","51.107.70.192/28","51.116.2.80/28","51.116.221.96/28","51.56.40.80/28","51.57.0.96/28","51.59.24.64/28","51.59.24.80/28","51.59.40.80/28","51.59.40.96/28","51.59.48.80/28","51.59.48.96/28","51.8.102.0/24","51.8.155.112/28","51.8.155.48/28","51.8.155.64/28","51.8.187.224/28","52.148.129.32/28","52.153.130.64/28","52.154.22.48/28","52.156.77.144/28","52.159.227.32/28","52.159.249.96/28","52.161.49.224/28","52.161.49.96/28","52.165.212.16/28","52.165.212.32/28","52.165.212.48/28","52.172.129.160/28","52.172.251.112/28","52.173.219.112/28","52.173.219.96/28","52.173.221.16/28","52.173.221.176/28","52.173.221.208/28","52.173.234.16/28","52.173.234.80/28","52.173.235.80/28","52.183.217.240/28","52.187.246.128/28","52.190.137.144/28","52.190.137.16/28","52.190.139.48/28","52.190.142.64/28","52.190.190.16/28","52.225.75.208/28","52.230.152.0/24","52.230.163.32/28","52.230.164.176/28","52.231.30.48/28","52.231.34.176/28","52.231.39.144/28","52.231.39.192/28","52.231.49.48/28","52.231.50.64/28","52.236.94.144/28","52.241.146.208/28","52.242.132.224/28","52.242.132.240/28","52.242.245.208/28","52.252.113.240/28","52.255.109.112/28","52.255.109.128/28","52.255.109.144/28","52.255.109.80/28","52.255.109.96/28","52.255.111.0/28","52.255.111.112/28","52.255.111.32/28","52.255.111.48/28","52.255.111.80/28","57.154.174.112/28","57.154.175.0/28","57.154.187.32/28","68.154.28.96/28","68.218.30.112/28","68.220.57.64/28","68.221.67.192/28","68.221.67.224/28","68.221.67.240/28","70.153.139.208/28","70.153.189.192/28","70.153.190.16/28","70.153.76.16/28","70.153.87.224/28","70.156.144.64/28","70.156.152.80/28","70.156.152.96/28","74.161.200.96/28","74.224.217.64/28","74.226.253.160/28","74.249.86.176/28","74.7.175.128/25","74.7.227.0/25","74.7.227.128/25","74.7.228.0/25","74.7.228.128/25","74.7.229.0/25","74.7.229.128/25","74.7.230.0/25","74.7.241.0/25","74.7.241.128/25","74.7.242.0/25","74.7.242.128/25","74.7.243.0/25","74.7.243.128/25","74.7.244.0/25","74.7.35.112/28","74.7.35.48/28","74.7.36.64/28","74.7.36.80/28","74.7.36.96/28","85.211.241.128/28","9.129.0.0/17","9.160.128.16/28","9.160.128.32/28","9.160.128.64/28","9.160.163.128/28","9.160.164.128/28","9.160.34.144/28","9.160.36.16/28","9.160.96.16/28","9.163.101.48/28","9.205.25.128/28","9.205.8.48/28","9.223.181.208/28","9.234.96.192/28","9.234.97.128/28","9.234.97.96/28","9.235.40.32/28"],Claude:["136.107.176.208/32","216.73.216.0/22","34.11.34.31/32","34.150.241.79/32","34.162.191.81/32","34.162.230.222/32","34.162.244.71/32","34.182.140.95/32","34.182.161.143/32","34.182.218.27/32","34.182.220.85/32","34.182.222.37/32","34.182.225.167/32","34.182.226.151/32","34.182.226.221/32","34.186.108.163/32","34.85.172.162/32","35.221.29.174/32","35.245.175.129/32","35.245.89.239/32"],Perplexity:["107.20.236.150/32","18.210.92.235/32","18.97.1.228/30","18.97.9.96/29","3.211.124.183/32","3.222.232.239/32","3.224.62.45/32","3.231.139.107/32"],Apple:["17.22.237.0/24","17.22.245.0/24","17.22.253.0/24","17.241.193.160/27","17.241.200.160/27","17.241.208.160/27","17.241.219.0/24","17.241.227.0/24","17.241.75.0/24","17.246.15.0/24","17.246.19.0/24","17.246.23.0/24"]},h=Object.keys(d);function p(t){let e=t.split(".");if(e.length!==4)return null;let i=0;for(let n of e){if(!/^\d{1,3}$/.test(n)||n.length>1&&n[0]==="0")return null;let o=Number(n);if(o>255)return null;i=i<<8|o;}return i>>>0}function x(t){let e=t,i=e.indexOf("%");if(i!==-1&&(e=e.slice(0,i)),!e||e.indexOf(":")===-1)return null;let n=null,o=e.lastIndexOf(":"),l=e.slice(o+1);if(l.indexOf(".")!==-1){if(n=p(l),n===null)return null;e=e.slice(0,o+1)+"0:0";}let r=e.split("::");if(r.length>2)return null;let s=r[0]?r[0].split(":"):[],c=r.length===2?r[1]?r[1].split(":"):[]:null,f;if(c===null){if(f=s,f.length!==8)return null}else {let a=8-s.length-c.length;if(a<0)return null;f=[...s,...Array(a).fill("0"),...c];}let u=0n;for(let a of f){if(!/^[0-9a-fA-F]{1,4}$/.test(a))return null;u=u<<16n|BigInt(parseInt(a,16));}return n!==null&&(u=u>>32n<<32n|BigInt(n>>>0)),u}function B(t){let e=new Map,i=[],n=[];for(let o of t){let l=o.lastIndexOf("/");if(l===-1)continue;let r=o.slice(0,l),s=Number(o.slice(l+1));if(!(!Number.isInteger(s)||s<0))if(r.indexOf(":")!==-1){if(s>128)continue;let c=x(r);if(c===null)continue;n.push({net:s===0?0n:c>>BigInt(128-s)<<BigInt(128-s),bits:s});}else {if(s>32)continue;let c=p(r);if(c===null)continue;let f=s===0?0:4294967295<<32-s>>>0,u={net:(c&f)>>>0,mask:f};if(s>=8){let a=u.net>>>24,g=e.get(a);g?g.push(u):e.set(a,[u]);}else i.push(u);}}return {v4:e,v4Wide:i,v6:n}}function R(t,e){if(!t)return false;let i=t.trim();if(!i)return false;if(i.indexOf(":")!==-1){let o=x(i);if(o===null)return false;let l=0xffffn<<32n;if(o>>32n===l>>32n){let r=Number(o&0xffffffffn)>>>0;if(m(r,e))return true}for(let r of e.v6)if(r.bits===0||o>>BigInt(128-r.bits)<<BigInt(128-r.bits)===r.net)return true;return false}let n=p(i);return n===null?false:m(n,e)}function m(t,e){let i=e.v4.get(t>>>24);if(i){for(let n of i)if((t&n.mask)>>>0===n.net)return true}for(let n of e.v4Wide)if((t&n.mask)>>>0===n.net)return true;return false}var A=/claude-code|perplexity-user/i,C=/ClaudeBot|Claude-SearchBot|GPTBot|OAI-SearchBot|ChatGPT-User|PerplexityBot|Applebot/i,y=new Map;function k(t){if(!(t in d))return;let e=y.get(t);return e||(e=B(d[t]??[]),y.set(t,e)),e}function V(){return h}function I(t,e){let i=t??"",n=b(t),o=k(n);if(!o){let s=n!=="Other"&&n!=="Browser";return {verdict:s?"unverifiable":"not-claimed",...s?{reason:"no-published-ranges"}:{},claimed:s?n:null,verified:null}}if(A.test(i)||!C.test(i))return {verdict:"unverifiable",reason:"client-side-agent",claimed:n,verified:null};let l=(e??"").trim();if(!l)return {verdict:"unverifiable",reason:"no-client-ip",claimed:n,verified:null};let r=R(l,o);return {verdict:r?"verified":"spoofed",claimed:n,verified:r}}function w(t){let i=(t.headers.get("x-forwarded-for")||"").split(",")[0]?.trim();return i||(t.headers.get("cf-connecting-ip")||t.headers.get("x-real-ip")||"").trim()}function D(t){return I(t.headers.get("user-agent"),w(t))}
2
- exports.clientIpFromRequest=w;exports.verifiableVendors=V;exports.verifyBotIdentity=I;exports.verifyRequest=D;//# sourceMappingURL=verify.cjs.map
1
+ 'use strict';function k(t){if(!t||typeof t!="string")return "Other";let e=t.toLowerCase();return e.includes("chatgpt-user")||e.includes("gptbot")||e.includes("oai-searchbot")||e.includes("openai")?"ChatGPT":e.includes("claudebot")||e.includes("claude-user")||e.includes("claude-searchbot")||e.includes("claude-web")||e.includes("anthropic")?"Claude":e.includes("perplexitybot")||e.includes("perplexity-user")?"Perplexity":e.includes("ccbot")?"Common Crawl":e.includes("google-extended")||e.includes("googlebot")||e.includes("google-cloudvertexbot")||e.includes("google-agent")||e.includes("googleagent-mariner")||e.includes("gemini-deep-research")?"Google":e.includes("applebot")?"Apple":e.includes("bingbot")?"Bing":e.includes("bytespider")?"Bytespider":e.includes("amazonbot")||e.includes("amzn-searchbot")||e.includes("novaact")?"Amazon":e.includes("meta-externalagent")||e.includes("meta-externalfetcher")||e.includes("meta-webindexer")||e.includes("facebookbot")?"Meta":e.includes("mistralai-user")?"Mistral":e.includes("duckassistbot")?"DuckDuckGo":e.includes("youbot")?"You.com":e.includes("diffbot")?"Diffbot":e.includes("ai2bot")?"AI2":e.includes("cohere")?"Cohere":e.includes("cursor")?"Cursor":e.includes("windsurf")?"Windsurf":e.includes("deepseek")?"DeepSeek":e.includes("pangubot")?"Huawei":e.includes("webzio")||e.includes("omgili")?"Webz.io":e.includes("timpibot")?"Timpi":e.includes("grok")||e.includes("xai-")?"xAI":e.includes("manus-user")?"Manus":e.includes("quillbot")?"QuillBot":e.includes("azureai-searchbot")?"Microsoft":e.includes("mycentralaiscraperbot")?"MyCentralAI":e.includes("petalbot")?"PetalBot":e.includes("ahrefsbot")?"Ahrefs":e.includes("semrushbot")?"Semrush":e.includes("mj12bot")?"Majestic":e.includes("dotbot")||e.includes("rogerbot")?"Moz":e.includes("screaming frog")?"Screaming Frog":e.includes("sitebulb")?"Sitebulb":e.includes("linkfluence")?"Linkfluence":e.includes("dataforseo")?"DataForSEO":e.includes("serpstatbot")?"Serpstat":e.includes("uptimerobot")?"UptimeRobot":e.includes("pingdom")?"Pingdom":e.includes("statuscake")?"StatusCake":e.includes("newrelicpinger")?"New Relic":e.includes("datadogagent")||e.includes("datadog")?"Datadog":e.includes("slackbot")?"Slack":e.includes("twitterbot")?"Twitter":e.includes("linkedinbot")?"LinkedIn":e.includes("discordbot")?"Discord":e.includes("telegrambot")?"Telegram":e.includes("whatsapp")?"WhatsApp":e.includes("linkupbot")?"Linkup":e.includes("sogou")?"Sogou":e.includes("yandexbot")?"Yandex":e.includes("baiduspider")?"Baidu":e.includes("facebookexternalhit")?"Facebook":e.includes("com.apple.webkit")?"Apple URL Preview":e.includes("ohdear")?"Oh Dear":e.includes("scrapy")?"Scrapy":e.includes("headlesschrome")?"Headless Chrome":e.includes("phantomjs")?"PhantomJS":e.includes("wget")?"wget":e.includes("httpie")?"HTTPie":e.includes("guzzlehttp")?"Guzzle":e.includes("electron/")?"Electron":/curl\//.test(e)?"curl":/axios\//.test(e)?"axios":/(?:^|[\s(])got(?:\/|[\s(])/.test(e)?"got":/\bcolly\b/.test(e)?"colly":/node-fetch\//.test(e)?"node-fetch":/python-requests\//.test(e)?"python-requests":/go-http-client\//.test(e)?"Go http client":/okhttp\//.test(e)?"OkHttp":/aiohttp\//.test(e)?"aiohttp":/deno\//.test(e)?"Deno":e.includes("mozilla")||e.includes("chrome")||e.includes("safari")||e.includes("firefox")?"Browser":"Other"}var h={ChatGPT:["104.208.184.192/28","104.208.184.208/28","104.210.139.192/28","104.210.139.224/28","104.210.140.128/28","128.85.198.32/28","13.65.138.112/28","13.65.138.96/28","13.67.72.16/28","13.70.107.160/28","13.71.2.208/28","13.76.115.224/28","13.76.115.240/28","13.76.116.80/28","13.76.32.208/28","13.83.167.128/28","13.83.237.176/28","132.196.82.48/28","132.196.86.0/24","134.149.233.80/28","135.116.136.160/28","135.13.64.240/28","135.220.73.208/28","135.220.73.240/28","135.234.64.0/24","135.237.131.208/28","135.237.133.48/28","137.135.191.176/28","138.91.30.48/28","138.91.46.96/28","145.132.1.32/28","145.132.136.96/28","145.133.0.176/28","158.158.5.32/28","168.63.252.240/28","172.162.248.64/28","172.170.1.80/28","172.170.225.0/28","172.170.241.80/28","172.170.8.208/28","172.171.4.176/28","172.175.152.224/28","172.178.140.144/28","172.178.141.112/28","172.178.141.128/28","172.182.193.224/28","172.182.193.80/28","172.182.194.144/28","172.182.194.32/28","172.182.195.48/28","172.182.202.0/25","172.182.204.0/24","172.182.207.0/25","172.182.209.208/28","172.182.211.192/28","172.182.213.192/28","172.182.214.0/24","172.182.215.0/24","172.182.224.0/28","172.183.143.224/28","172.183.222.128/28","172.192.112.208/28","172.192.88.192/28","172.192.97.32/28","172.197.160.192/28","172.197.161.208/28","172.197.170.80/28","172.197.203.16/28","172.199.137.80/28","172.202.102.112/28","172.203.190.128/28","172.204.27.16/28","172.204.28.224/28","172.204.96.32/28","172.204.96.48/28","172.204.96.80/28","172.205.189.192/28","172.207.1.32/28","172.208.128.32/28","172.208.128.48/28","172.212.159.64/28","172.212.172.160/28","172.213.21.16/28","172.215.215.32/28","172.215.218.96/28","191.233.1.112/28","191.233.1.128/28","191.233.194.32/28","191.233.196.112/28","191.233.199.160/28","191.233.2.0/28","191.235.66.16/28","191.235.99.80/28","191.237.249.64/28","191.239.245.16/28","20.102.212.144/28","20.113.211.112/28","20.113.225.112/28","20.125.112.224/28","20.125.144.144/28","20.125.66.80/28","20.14.99.96/28","20.161.75.208/28","20.168.18.32/28","20.168.7.192/28","20.168.7.240/28","20.169.6.224/28","20.169.7.48/28","20.169.72.112/28","20.169.73.176/28","20.169.73.32/28","20.169.73.64/28","20.169.77.0/25","20.169.78.112/28","20.169.78.128/28","20.169.78.144/28","20.169.78.160/28","20.169.78.176/28","20.169.78.192/28","20.169.78.208/28","20.169.78.48/28","20.169.78.64/28","20.169.78.80/28","20.169.78.96/28","20.169.86.224/28","20.169.86.240/28","20.169.87.112/28","20.17.108.96/28","20.170.184.16/28","20.170.184.32/28","20.170.184.48/28","20.170.184.64/28","20.170.184.80/28","20.171.123.64/28","20.171.206.0/24","20.171.207.0/24","20.171.53.224/28","20.172.29.32/28","20.193.233.240/28","20.193.50.32/28","20.194.0.208/28","20.194.1.0/28","20.198.67.96/28","20.199.211.160/28","20.199.242.0/28","20.200.212.240/28","20.204.24.240/28","20.210.154.128/28","20.210.174.208/28","20.210.211.192/28","20.215.187.208/28","20.215.188.192/28","20.215.214.16/28","20.215.219.128/28","20.215.219.160/28","20.215.219.208/28","20.215.220.112/28","20.215.220.128/28","20.215.220.144/28","20.215.220.160/28","20.215.220.176/28","20.215.220.192/28","20.215.220.208/28","20.215.220.64/28","20.215.220.80/28","20.215.220.96/28","20.218.30.240/28","20.219.71.192/28","20.222.36.192/28","20.226.32.80/28","20.227.140.32/28","20.228.106.176/28","20.235.75.208/28","20.235.87.224/28","20.249.63.208/28","20.25.151.224/28","20.250.136.64/28","20.250.136.80/28","20.250.6.128/28","20.27.94.128/28","20.42.10.176/28","20.45.178.144/28","20.48.120.208/28","20.52.125.160/28","20.55.129.0/28","20.55.229.144/28","20.57.199.192/28","20.63.180.96/28","20.63.221.64/28","20.79.59.112/28","20.81.183.64/28","20.83.243.176/28","20.97.189.96/28","23.102.140.144/28","23.102.141.32/28","23.98.142.176/28","23.98.179.16/28","23.98.186.176/28","23.98.186.192/28","23.98.186.64/28","23.98.186.96/28","4.151.119.48/28","4.151.241.240/28","4.151.71.176/28","4.189.118.208/28","4.189.119.48/28","4.196.118.112/28","4.197.115.112/28","4.197.19.176/28","4.197.22.112/28","4.197.64.0/28","4.197.64.16/28","4.197.64.48/28","4.197.64.64/28","4.198.72.16/28","4.198.96.112/28","4.201.232.64/28","4.201.232.80/28","4.203.96.80/28","4.205.128.176/28","4.218.24.64/28","4.226.200.16/28","4.226.226.32/28","4.227.36.0/25","40.116.73.208/28","40.122.235.112/28","40.67.175.0/25","40.67.183.160/28","40.67.183.176/28","40.78.161.48/28","40.81.134.128/28","40.81.134.144/28","40.81.234.144/28","40.81.67.96/28","40.84.181.32/28","40.84.221.208/28","40.84.221.224/28","40.90.214.16/28","48.193.44.32/28","48.221.184.112/28","48.221.184.80/28","48.221.184.96/28","48.221.40.176/28","51.107.70.192/28","51.116.2.80/28","51.116.221.96/28","51.56.40.80/28","51.57.0.96/28","51.59.24.64/28","51.59.24.80/28","51.59.40.80/28","51.59.40.96/28","51.59.48.80/28","51.59.48.96/28","51.8.102.0/24","51.8.155.112/28","51.8.155.48/28","51.8.155.64/28","51.8.187.224/28","52.148.129.32/28","52.153.130.64/28","52.154.22.48/28","52.156.77.144/28","52.159.227.32/28","52.159.249.96/28","52.161.49.224/28","52.161.49.96/28","52.165.212.16/28","52.165.212.32/28","52.165.212.48/28","52.172.129.160/28","52.172.251.112/28","52.173.219.112/28","52.173.219.96/28","52.173.221.16/28","52.173.221.176/28","52.173.221.208/28","52.173.234.16/28","52.173.234.80/28","52.173.235.80/28","52.183.217.240/28","52.187.246.128/28","52.190.137.144/28","52.190.137.16/28","52.190.139.48/28","52.190.142.64/28","52.190.190.16/28","52.225.75.208/28","52.230.152.0/24","52.230.163.32/28","52.230.164.176/28","52.231.30.48/28","52.231.34.176/28","52.231.39.144/28","52.231.39.192/28","52.231.49.48/28","52.231.50.64/28","52.236.94.144/28","52.241.146.208/28","52.242.132.224/28","52.242.132.240/28","52.242.245.208/28","52.252.113.240/28","52.255.109.112/28","52.255.109.128/28","52.255.109.144/28","52.255.109.80/28","52.255.109.96/28","52.255.111.0/28","52.255.111.112/28","52.255.111.32/28","52.255.111.48/28","52.255.111.80/28","57.154.174.112/28","57.154.175.0/28","57.154.187.32/28","68.154.28.96/28","68.218.30.112/28","68.220.57.64/28","68.221.67.192/28","68.221.67.224/28","68.221.67.240/28","70.153.139.208/28","70.153.189.192/28","70.153.190.16/28","70.153.76.16/28","70.153.87.224/28","70.156.144.64/28","70.156.152.80/28","70.156.152.96/28","74.161.200.96/28","74.224.217.64/28","74.226.253.160/28","74.249.86.176/28","74.7.175.128/25","74.7.227.0/25","74.7.227.128/25","74.7.228.0/25","74.7.228.128/25","74.7.229.0/25","74.7.229.128/25","74.7.230.0/25","74.7.241.0/25","74.7.241.128/25","74.7.242.0/25","74.7.242.128/25","74.7.243.0/25","74.7.243.128/25","74.7.244.0/25","74.7.35.112/28","74.7.35.48/28","74.7.36.64/28","74.7.36.80/28","74.7.36.96/28","85.211.241.128/28","9.129.0.0/17","9.160.128.16/28","9.160.128.32/28","9.160.128.64/28","9.160.163.128/28","9.160.164.128/28","9.160.34.144/28","9.160.36.16/28","9.160.96.16/28","9.163.101.48/28","9.205.25.128/28","9.205.8.48/28","9.223.181.208/28","9.234.96.192/28","9.234.97.128/28","9.234.97.96/28","9.235.40.32/28"],Claude:["136.107.176.208/32","216.73.216.0/22","34.11.34.31/32","34.150.241.79/32","34.162.191.81/32","34.162.230.222/32","34.162.244.71/32","34.182.140.95/32","34.182.161.143/32","34.182.218.27/32","34.182.220.85/32","34.182.222.37/32","34.182.225.167/32","34.182.226.151/32","34.182.226.221/32","34.186.108.163/32","34.85.172.162/32","35.221.29.174/32","35.245.175.129/32","35.245.89.239/32"],Perplexity:["107.20.236.150/32","18.210.92.235/32","18.97.1.228/30","18.97.9.96/29","3.211.124.183/32","3.222.232.239/32","3.224.62.45/32","3.231.139.107/32"],Apple:["17.22.237.0/24","17.22.245.0/24","17.22.253.0/24","17.241.193.160/27","17.241.200.160/27","17.241.208.160/27","17.241.219.0/24","17.241.227.0/24","17.241.75.0/24","17.246.15.0/24","17.246.19.0/24","17.246.23.0/24"]},x=Object.keys(h);function m(t){let e=t.split(".");if(e.length!==4)return null;let r=0;for(let n of e){if(!/^\d{1,3}$/.test(n)||n.length>1&&n[0]==="0")return null;let u=Number(n);if(u>255)return null;r=r<<8|u;}return r>>>0}function w(t){let e=t,r=e.indexOf("%");if(r!==-1&&(e=e.slice(0,r)),!e||e.indexOf(":")===-1)return null;let n=null,u=e.lastIndexOf(":"),s=e.slice(u+1);if(s.indexOf(".")!==-1){if(n=m(s),n===null)return null;e=e.slice(0,u+1)+"0:0";}let i=e.split("::");if(i.length>2)return null;let o=i[0]?i[0].split(":"):[],c=i.length===2?i[1]?i[1].split(":"):[]:null,a;if(c===null){if(a=o,a.length!==8)return null}else {let d=8-o.length-c.length;if(d<0)return null;a=[...o,...Array(d).fill("0"),...c];}let l=0n;for(let d of a){if(!/^[0-9a-fA-F]{1,4}$/.test(d))return null;l=l<<16n|BigInt(parseInt(d,16));}return n!==null&&(l=l>>32n<<32n|BigInt(n>>>0)),l}function A(t){let e=new Map,r=[],n=[];for(let u of t){let s=u.lastIndexOf("/");if(s===-1)continue;let i=u.slice(0,s),o=Number(u.slice(s+1));if(!(!Number.isInteger(o)||o<0))if(i.indexOf(":")!==-1){if(o>128)continue;let c=w(i);if(c===null)continue;n.push({net:o===0?0n:c>>BigInt(128-o)<<BigInt(128-o),bits:o});}else {if(o>32)continue;let c=m(i);if(c===null)continue;let a=o===0?0:4294967295<<32-o>>>0,l={net:(c&a)>>>0,mask:a};if(o>=8){let d=l.net>>>24,f=e.get(d);f?f.push(l):e.set(d,[l]);}else r.push(l);}}return {v4:e,v4Wide:r,v6:n}}function B(t,e){if(!t)return false;let r=t.trim();if(!r)return false;if(r.indexOf(":")!==-1){let u=w(r);if(u===null)return false;let s=0xffffn<<32n;if(u>>32n===s>>32n){let i=Number(u&0xffffffffn)>>>0;if(v(i,e))return true}for(let i of e.v6)if(i.bits===0||u>>BigInt(128-i.bits)<<BigInt(128-i.bits)===i.net)return true;return false}let n=m(r);return n===null?false:v(n,e)}function v(t,e){let r=e.v4.get(t>>>24);if(r){for(let n of r)if((t&n.mask)>>>0===n.net)return true}for(let n of e.v4Wide)if((t&n.mask)>>>0===n.net)return true;return false}var S="/.well-known/http-message-signatures-directory",g=new Map;function T(t){if(!t)return null;let e=t.trim().match(/^"([^"]*)"$/);return e?e[1]??null:null}function E(t){if(!t)return null;let e=t.trim().match(/^([A-Za-z0-9_-]+)=(\((.*?)\)(.*))$/);if(!e)return null;let[,r,n,u,s]=e;if(!r||n===void 0)return null;let i=(u??"").match(/"[^"]*"(?:;[^ )]*)?/g)?.map(c=>c)??[],o={label:r,components:i,raw:n};for(let c of (s??"").split(";")){let a=c.match(/^([a-z]+)=(.*)$/);if(!a)continue;let[,l,d]=a,f=d??"",p=f.startsWith('"')?f.slice(1,-1):f;l==="keyid"?o.keyid=p:l==="alg"?o.alg=p:l==="tag"?o.tag=p:l==="created"?o.created=Number(p):l==="expires"&&(o.expires=Number(p));}return o}function P(t,e){if(!t)return null;let r=t.match(new RegExp(`(?:^|,)\\s*${e}=:([A-Za-z0-9+/=]+):`));if(!r?.[1])return null;try{let n=atob(r[1]),u=new ArrayBuffer(n.length),s=new Uint8Array(u);for(let i=0;i<n.length;i++)s[i]=n.charCodeAt(i);return u}catch{return null}}function V(t,e){let r=new URL(t.url),n=[];for(let u of e.components){let s=u.match(/^"([^"]*)"/)?.[1];if(s===void 0)return null;let i;switch(s){case "@method":i=t.method.toUpperCase();break;case "@authority":i=r.host;break;case "@scheme":i=r.protocol.replace(":","");break;case "@target-uri":i=r.toString();break;case "@path":i=r.pathname;break;case "@query":i=r.search||"?";break;default:if(s.startsWith("@"))return null;i=t.headers.get(s);break}if(i===null)return null;n.push(`${u}: ${i.trim()}`);}return n.push(`"@signature-params": ${e.raw}`),n.join(`
2
+ `)}async function R(t,e){let r=e.fetchImpl??fetch,n=e.keyTtlMs??36e5,u=await r(`${t}${S}`,{headers:{accept:"application/http-message-signatures-directory+json, application/json"}});if(!u.ok)throw new Error(`key directory ${u.status}`);let s=await u.json(),i=new Map;for(let o of s.keys??[]){let c=o;if(!(c.kty!=="OKP"||c.crv!=="Ed25519"))try{let a=await crypto.subtle.importKey("jwk",c,{name:"Ed25519"},!1,["verify"]);c.kid&&i.set(c.kid,a),i.set(await C(c),a);}catch{}}return {keys:i,expiresAt:Date.now()+n}}async function C(t){let e=JSON.stringify({crv:t.crv,kty:t.kty,x:t.x}),r=await crypto.subtle.digest("SHA-256",new TextEncoder().encode(e)),n="";for(let u of new Uint8Array(r))n+=String.fromCharCode(u);return btoa(n).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function I(t,e){let r=g.get(t);if(r)return r.then(u=>u.expiresAt>Date.now()?u:(g.delete(t),I(t,e))).catch(()=>(g.delete(t),R(t,e)));let n=R(t,e);return g.set(t,n),n.catch(()=>g.delete(t)),n}function W(){g.clear();}async function b(t,e={}){let r=t.headers.get("signature-agent"),n=t.headers.get("signature-input"),u=t.headers.get("signature");if(!n||!u)return {verdict:"not-signed"};let s=E(n);if(!s)return {verdict:"malformed"};if(s.tag&&s.tag!=="web-bot-auth")return {verdict:"not-signed"};let i=T(r);if(!i)return {verdict:"malformed"};let o;try{let y=new URL(i);if(y.protocol!=="https:")return {verdict:"malformed"};o=y.origin;}catch{return {verdict:"malformed"}}if(e.allowedSigners&&!e.allowedSigners.includes(o))return {verdict:"unknown-key",signerOrigin:o,...s.keyid?{keyId:s.keyid}:{}};let c=Math.floor(Date.now()/1e3),a=e.maxAgeSeconds??300;if(s.expires!==void 0&&s.expires<c)return {verdict:"expired",signerOrigin:o};if(s.created!==void 0&&c-s.created>a)return {verdict:"expired",signerOrigin:o};let l=V(t,s);if(!l)return {verdict:"malformed",signerOrigin:o};let d=P(u,s.label);if(!d)return {verdict:"malformed",signerOrigin:o};let f;try{f=await I(o,e);}catch{return {verdict:"unknown-key",signerOrigin:o}}let p=s.keyid?f.keys.get(s.keyid):void 0;return p?{verdict:await crypto.subtle.verify({name:"Ed25519"},p,d,new TextEncoder().encode(l))?"verified":"invalid-signature",signerOrigin:o,...s.keyid?{keyId:s.keyid}:{}}:{verdict:"unknown-key",signerOrigin:o,...s.keyid?{keyId:s.keyid}:{}}}function D(t={}){return async e=>{let r=await b(e,t);switch(r.verdict){case "verified":return {verdict:"verified",verified:true,reason:"web-bot-auth"};case "invalid-signature":return {verdict:"spoofed",verified:false,reason:"web-bot-auth-signature-invalid"};case "expired":return {verdict:"spoofed",verified:false,reason:"web-bot-auth-expired"};case "malformed":case "unknown-key":return {verdict:"unverifiable",verified:null,reason:`web-bot-auth-${r.verdict}`};default:return {verdict:"unverifiable",verified:null,reason:"not-signed"}}}}var _=/claude-code|perplexity-user/i,M=/ClaudeBot|Claude-SearchBot|GPTBot|OAI-SearchBot|ChatGPT-User|PerplexityBot|Applebot/i,O=new Map;function N(t){if(!(t in h))return;let e=O.get(t);return e||(e=A(h[t]??[]),O.set(t,e)),e}function J(){return x}function z(t,e){let r=t??"",n=k(t),u=N(n);if(!u){let o=n!=="Other"&&n!=="Browser";return {verdict:o?"unverifiable":"not-claimed",...o?{reason:"no-published-ranges"}:{},claimed:o?n:null,verified:null}}if(_.test(r)||!M.test(r))return {verdict:"unverifiable",reason:"client-side-agent",claimed:n,verified:null};let s=(e??"").trim();if(!s)return {verdict:"unverifiable",reason:"no-client-ip",claimed:n,verified:null};let i=B(s,u);return {verdict:i?"verified":"spoofed",claimed:n,verified:i}}function L(t){let r=(t.headers.get("x-forwarded-for")||"").split(",")[0]?.trim();return r||(t.headers.get("cf-connecting-ip")||t.headers.get("x-real-ip")||"").trim()}function G(t){return z(t.headers.get("user-agent"),L(t))}function Q(t={}){return async e=>{let r=await b(e,t);if(r.verdict==="verified")return {verdict:"verified",verified:true,reason:"web-bot-auth"};if(r.verdict==="invalid-signature"||r.verdict==="expired")return {verdict:"spoofed",verified:false,reason:`web-bot-auth-${r.verdict}`};let n=G(e);return n.verdict!=="not-claimed"&&n.verdict!=="unverifiable"?{...n,reason:n.reason??"published-ip-range"}:n}}
3
+ exports.clearKeyCache=W;exports.clientIpFromRequest=L;exports.combinedVerifier=Q;exports.jwkThumbprint=C;exports.verifiableVendors=J;exports.verifyBotIdentity=z;exports.verifyRequest=G;exports.verifyWebBotAuth=b;exports.webBotAuthVerifier=D;//# sourceMappingURL=verify.cjs.map
3
4
  //# sourceMappingURL=verify.cjs.map