{"_id":"@cunpingtai/ad-traffic-guard","_rev":"3-2dedf67381f11b8f02c9f07805409ccb","name":"@cunpingtai/ad-traffic-guard","dist-tags":{"latest":"0.2.1"},"versions":{"0.1.0":{"name":"@cunpingtai/ad-traffic-guard","version":"0.1.0","keywords":["adsense","invalid-traffic","lazy-load","advertising","bot","nextjs","react"],"author":{"name":"cunpingtai"},"license":"MIT","_id":"@cunpingtai/ad-traffic-guard@0.1.0","maintainers":[{"name":"linweixian","email":"825490932@qq.com"}],"homepage":"https://github.com/cunpingtai/ad-traffic-guard#readme","bugs":{"url":"https://github.com/cunpingtai/ad-traffic-guard/issues"},"dist":{"shasum":"8eceb44a2cc4d902f4e6aee332b8798da4bb825b","tarball":"https://registry.npmjs.org/@cunpingtai/ad-traffic-guard/-/ad-traffic-guard-0.1.0.tgz","fileCount":59,"integrity":"sha512-5ab/uXVVHMP59ClarCfVmd+/23OJMS/Yap+fi9kpE5MFCUfWnVMBw8cMztUaWpxIpTxhAvOdH8jBc0SIRGotFg==","signatures":[{"sig":"MEYCIQDHd1IuCVJjizd+Nk3IaNtPPnoRJR8usVRi1wETa6xElgIhANgWYCLgfjlQpsp/KPdJGLQw6VHAuv1+dm2yfRW5F+a+","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":61121},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./core":{"types":"./dist/core/index.d.ts","import":"./dist/core/index.js"},"./react":{"types":"./dist/react/index.d.ts","import":"./dist/react/index.js"}},"gitHead":"4d8f3a26e5ca72bda20752dfd0879acb58dba6bc","scripts":{"test":"npm run build && node --test tests/*.test.mjs","build":"npm run clean && tsc -p tsconfig.json","clean":"rm -rf dist .tmp-test","typecheck":"tsc -p tsconfig.json --noEmit","pack:check":"npm pack --dry-run","prepublishOnly":"npm run build"},"_npmUser":{"name":"linweixian","email":"825490932@qq.com"},"repository":{"url":"git+https://github.com/cunpingtai/ad-traffic-guard.git","type":"git"},"_npmVersion":"10.8.2","description":"A browser-side ad eligibility gate that delays AdSense loading until traffic shows minimum human-quality signals.","directories":{},"sideEffects":false,"_nodeVersion":"20.20.2","publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.8.0","@types/react":"^19.0.0"},"peerDependencies":{"react":">=18.2.0"},"peerDependenciesMeta":{"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/ad-traffic-guard_0.1.0_1786612453207_0.3379336592277622","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@cunpingtai/ad-traffic-guard","version":"0.2.0","keywords":["adsense","invalid-traffic","lazy-load","advertising","bot","botd","isbot","nextjs","react"],"author":{"name":"cunpingtai"},"license":"MIT","_id":"@cunpingtai/ad-traffic-guard@0.2.0","maintainers":[{"name":"linweixian","email":"825490932@qq.com"}],"homepage":"https://github.com/cunpingtai/ad-traffic-guard#readme","bugs":{"url":"https://github.com/cunpingtai/ad-traffic-guard/issues"},"dist":{"shasum":"c10ad056500971a21bf7671bd441fb971edb31b1","tarball":"https://registry.npmjs.org/@cunpingtai/ad-traffic-guard/-/ad-traffic-guard-0.2.0.tgz","fileCount":75,"integrity":"sha512-akLsiwja0uxpPv1JEP+fI9OWy3yxUi+CkpiDBaP4c6NF+bF0tpPMCwr7HmrMV6IX0vHqIW8SV5w5YAobufxFsw==","signatures":[{"sig":"MEUCIQDdG1VucHEWvyAbOvU+rpySpksVsDE57Lkjn0Bu3de3jQIgZLiWoz6k+Y2BUmatm2ikK7y1/4NhKbYsJazYhHBPBGs=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":82588},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./core":{"types":"./dist/core/index.d.ts","import":"./dist/core/index.js"},"./react":{"types":"./dist/react/index.d.ts","import":"./dist/react/index.js"},"./server":{"types":"./dist/server/index.d.ts","import":"./dist/server/index.js"},"./detectors":{"types":"./dist/detectors/index.d.ts","import":"./dist/detectors/index.js"}},"gitHead":"4d8f3a26e5ca72bda20752dfd0879acb58dba6bc","scripts":{"test":"npm run build && node --test tests/*.test.mjs","build":"npm run clean && tsc -p tsconfig.json","clean":"rm -rf dist .tmp-test","typecheck":"tsc -p tsconfig.json --noEmit","pack:check":"npm pack --dry-run","prepublishOnly":"npm run build"},"_npmUser":{"name":"linweixian","email":"825490932@qq.com"},"repository":{"url":"git+https://github.com/cunpingtai/ad-traffic-guard.git","type":"git"},"_npmVersion":"10.8.2","description":"AdSense eligibility orchestrator: isbot + BotD + visibility/engagement gating before loading ads.","directories":{},"sideEffects":false,"_nodeVersion":"20.20.2","dependencies":{"isbot":"^5.2.1","@fingerprintjs/botd":"^2.0.0"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.8.0","@types/react":"^19.0.0"},"peerDependencies":{"react":">=18.2.0"},"peerDependenciesMeta":{"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/ad-traffic-guard_0.2.0_1786613928602_0.0969857592030714","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"@cunpingtai/ad-traffic-guard","version":"0.2.1","description":"AdSense eligibility orchestrator: isbot + BotD + visibility/engagement gating before loading ads. Default passthrough mode currently allows all ads.","type":"module","sideEffects":false,"license":"MIT","author":{"name":"cunpingtai"},"homepage":"https://github.com/cunpingtai/ad-traffic-guard#readme","bugs":{"url":"https://github.com/cunpingtai/ad-traffic-guard/issues"},"repository":{"type":"git","url":"git+https://github.com/cunpingtai/ad-traffic-guard.git"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"main":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./core":{"types":"./dist/core/index.d.ts","import":"./dist/core/index.js"},"./react":{"types":"./dist/react/index.d.ts","import":"./dist/react/index.js"},"./server":{"types":"./dist/server/index.d.ts","import":"./dist/server/index.js"},"./detectors":{"types":"./dist/detectors/index.d.ts","import":"./dist/detectors/index.js"}},"scripts":{"clean":"rm -rf dist .tmp-test","build":"npm run clean && tsc -p tsconfig.json","typecheck":"tsc -p tsconfig.json --noEmit","prepublishOnly":"npm run build","test":"npm run build && node --test tests/*.test.mjs","pack:check":"npm pack --dry-run"},"peerDependencies":{"react":">=18.2.0"},"peerDependenciesMeta":{"react":{"optional":true}},"dependencies":{"@fingerprintjs/botd":"^2.0.0","isbot":"^5.2.1"},"devDependencies":{"@types/react":"^19.0.0","typescript":"^5.8.0"},"keywords":["adsense","invalid-traffic","lazy-load","advertising","bot","botd","isbot","nextjs","react"],"engines":{"node":">=18"},"_id":"@cunpingtai/ad-traffic-guard@0.2.1","gitHead":"f423512255bd3f629736c8e774d2502d9fff3ba4","_nodeVersion":"20.20.2","_npmVersion":"10.8.2","dist":{"integrity":"sha512-ghltZBP7ommImCXZ6Gg+4whp6u1bx6mGBNIEirvrIHidX+AA7Hk9uVu7EXnaEW+/Z78medKjERH4OX7sRg7rCg==","shasum":"ae6a4bf04169f7815cf126557efaf15472e9c048","tarball":"https://registry.npmjs.org/@cunpingtai/ad-traffic-guard/-/ad-traffic-guard-0.2.1.tgz","fileCount":75,"unpackedSize":84215,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCxTdg6g5kM/MENxyiPLff1HqkgJI1RxmdCcNsa3O+wdgIhALowbhF2l7iwVzplFCs6kVKYxRru3DcW2/r3/6x3q+YW"}]},"_npmUser":{"name":"linweixian","email":"825490932@qq.com"},"directories":{},"maintainers":[{"name":"linweixian","email":"825490932@qq.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/ad-traffic-guard_0.2.1_1786729921185_0.10441553571916584"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-13T09:14:13.041Z","modified":"2026-08-14T17:52:01.613Z","0.1.0":"2026-08-13T09:14:13.385Z","0.2.0":"2026-08-13T09:38:48.753Z","0.2.1":"2026-08-14T17:52:01.426Z"},"bugs":{"url":"https://github.com/cunpingtai/ad-traffic-guard/issues"},"author":{"name":"cunpingtai"},"license":"MIT","homepage":"https://github.com/cunpingtai/ad-traffic-guard#readme","keywords":["adsense","invalid-traffic","lazy-load","advertising","bot","botd","isbot","nextjs","react"],"repository":{"type":"git","url":"git+https://github.com/cunpingtai/ad-traffic-guard.git"},"description":"AdSense eligibility orchestrator: isbot + BotD + visibility/engagement gating before loading ads. Default passthrough mode currently allows all ads.","maintainers":[{"name":"linweixian","email":"825490932@qq.com"}],"readme":"# @cunpingtai/ad-traffic-guard\n\nAdSense **eligibility orchestrator** for multi-site publishers.\n\nIt does **not** try to reinvent bot detection. Instead:\n\n1. **`isbot`** (server/edge) — known crawlers that declare themselves\n2. **`@fingerprintjs/botd`** (browser) — Headless/Selenium/Playwright-style automation\n3. **Optional Cloudflare / external signals** — network bot reputation when you have it\n4. **This package** — visible time, trusted interaction, frequency, SDK + slot lazy load\n\nIt does **not** promise to catch every bot, replace Google's invalid-traffic systems, or guarantee an AdSense account will never be limited. Goal: reduce low-confidence ad requests while keeping the site fully accessible.\n\n## Install\n\n```bash\nnpm install @cunpingtai/ad-traffic-guard\n```\n\n## Next.js App Router\n\n### 1. Middleware — mark known crawlers with `isbot`\n\n```ts\n// middleware.ts\nimport { NextResponse, type NextRequest } from \"next/server\";\nimport {\n  ATG_KNOWN_CRAWLER_HEADER,\n  isKnownCrawlerRequest\n} from \"@cunpingtai/ad-traffic-guard/server\";\n\nexport function middleware(request: NextRequest) {\n  const response = NextResponse.next();\n\n  if (isKnownCrawlerRequest(request.headers)) {\n    response.headers.set(ATG_KNOWN_CRAWLER_HEADER, \"1\");\n    response.cookies.set(\"atg-known-crawler\", \"1\", {\n      path: \"/\",\n      maxAge: 60 * 60,\n      sameSite: \"lax\"\n    });\n  }\n\n  return response;\n}\n```\n\n`isbot === false` only means **unknown**, not human.\n\n### 2. Client monetization wrapper\n\n```tsx\n\"use client\";\n\nimport { usePathname } from \"next/navigation\";\nimport {\n  AdSenseLoader,\n  AdTrafficGuardProvider,\n  LazyAdSlot\n} from \"@cunpingtai/ad-traffic-guard/react\";\n\nconst client = \"ca-pub-XXXXXXXXXXXXXXXX\";\n\nexport function Monetization({ children }: { children: React.ReactNode }) {\n  const pathname = usePathname();\n  const consentGranted = true; // Replace with your CMP state.\n\n  return (\n    <AdTrafficGuardProvider\n      routeKey={pathname}\n      // Optional: pass Cloudflare Enterprise bot score / verified bot here\n      // externalSignals={{ cfBotScore, verifiedBot, knownBot }}\n    >\n      <AdSenseLoader client={client} consentGranted={consentGranted} />\n      {children}\n      <LazyAdSlot\n        client={client}\n        slot=\"1234567890\"\n        consentGranted={consentGranted}\n        rootMargin=\"800px 0px\"\n      />\n    </AdTrafficGuardProvider>\n  );\n}\n```\n\nWrap from `app/layout.tsx`. **Remove** any static AdSense SDK tag from `<head>`:\n\n```html\n<script async src=\"https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?...\">\n```\n\n## Default policy\n\n> **Current default: `passthrough: true`.**  \n> All eligibility checks are skipped and ads are allowed immediately (including Auto ads script load). Set `passthrough: false` when you want the guard back on.\n\n| Traffic state | Behavior when `passthrough: false` |\n|---|---|\n| `isbot` / known-crawler cookie / verified bot | Block ads |\n| BotD reports browser automation | Block ads |\n| BotD still running / detector error | Wait (fail closed) |\n| Cloudflare score `< 30` (when provided) | High risk → no ads |\n| localhost / loopback | Block ads |\n| Hidden / prerender page | Wait |\n| Trusted interaction + >= 5s visible | Eligible if risk is not high |\n| Focused passive reader + >= 15s visible | Eligible if risk is not high |\n| High page/reload frequency | Require >= 20s visible |\n| Still unqualified after 30s | Keep ads disabled for page view |\n\n## Customize\n\n```tsx\n<AdTrafficGuardProvider\n  routeKey={pathname}\n  // Re-enable gating later:\n  config={{ passthrough: false }}\n  externalSignals={{ knownBot, cfBotScore, verifiedBot }}\n>\n  {children}\n</AdTrafficGuardProvider>\n```\n\n## Read the decision\n\n```tsx\n\"use client\";\n\nimport { useAdEligibility } from \"@cunpingtai/ad-traffic-guard/react\";\n\nexport function DebugAdTrafficGuard() {\n  const { result } = useAdEligibility();\n  return <pre>{JSON.stringify(result, null, 2)}</pre>;\n}\n```\n\n## Framework-agnostic usage\n\n```ts\nimport { createAdTrafficGuard } from \"@cunpingtai/ad-traffic-guard/core\";\n\nconst guard = createAdTrafficGuard({\n  externalSignals: { knownBot: false }\n});\n\nguard.subscribe((result) => {\n  if (result.allowed) {\n    console.log(\"Eligible to request ads\");\n  }\n});\n\nguard.start();\n```\n\n## Consent / CMP\n\nTraffic eligibility and consent are separate gates:\n\n```tsx\n<AdSenseLoader client=\"ca-pub-XXXXXXXXXXXXXXXX\" consentGranted={cmpAllowsAds} />\n```\n\n## Logging recommendation\n\nDo not send raw IPs or invasive fingerprints. Aggregate:\n\n```ts\n{\n  site: location.hostname,\n  path: location.pathname,\n  status: result.status,\n  risk: result.risk,\n  score: result.score,\n  reason: result.reason,\n  visibleMs: result.signals.visibleMs,\n  interacted: result.signals.hadTrustedInteraction,\n  browserAutomation: result.signals.browserAutomation,\n  knownBot: result.signals.knownBot,\n  cfBotScore: result.signals.cfBotScore\n}\n```\n\n## Development\n\n```bash\nnpm install\nnpm test\nnpm run pack:check\n```\n\n## Publishing\n\n```bash\nnpm publish --access public --otp=YOUR_OTP\n```\n\nRepository: [github.com/cunpingtai/ad-traffic-guard](https://github.com/cunpingtai/ad-traffic-guard)\n","readmeFilename":"README.md"}