{"_id":"@anomira/browser-sdk","_rev":"4-6ec6f1057cf092534d8d3cdb37259503","name":"@anomira/browser-sdk","dist-tags":{"latest":"0.1.3"},"versions":{"0.1.0":{"name":"@anomira/browser-sdk","version":"0.1.0","license":"MIT","_id":"@anomira/browser-sdk@0.1.0","maintainers":[{"name":"muyiwaloba","email":"jadcreationsltd@gmail.com"}],"dist":{"shasum":"eccb75beac45bf8430d0afd3eea1e3928d54c645","tarball":"https://registry.npmjs.org/@anomira/browser-sdk/-/browser-sdk-0.1.0.tgz","fileCount":7,"integrity":"sha512-g/pOKkY7GHSBaiS/0bcyC8Bb5fYBW1SaTmVrxcTfYeZ5ZCUheP1TvCyBBrpNcAtTDIyip05SMrkMUMSewl6Vwg==","signatures":[{"sig":"MEYCIQCl1RF6Nd5vP8WqRlOWT7f7iNmzw2iWFsiMJjgUD42sZAIhAI3GrO+sUfI0RNSFWizoYDxK/6faAFNu1aedMqIneOOJ","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":71992},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}}},"gitHead":"c06883ab1eca4e26e1dafd21c2911bcc96be74c1","scripts":{"dev":"tsup --watch","build":"tsup","typecheck":"tsc --noEmit"},"_npmUser":{"name":"muyiwaloba","email":"jadcreationsltd@gmail.com"},"_npmVersion":"10.8.2","description":"Anomira browser-side fingerprinting, bot detection, and form guard SDK","directories":{},"_nodeVersion":"20.20.2","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.2","typescript":"^5.4.5"},"_npmOperationalInternal":{"tmp":"tmp/browser-sdk_0.1.0_1779208733489_0.30059569484763315","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@anomira/browser-sdk","version":"0.1.1","license":"MIT","_id":"@anomira/browser-sdk@0.1.1","maintainers":[{"name":"muyiwaloba","email":"jadcreationsltd@gmail.com"}],"dist":{"shasum":"8957bfc305e7d3f24dade56d6bd9f018033a5744","tarball":"https://registry.npmjs.org/@anomira/browser-sdk/-/browser-sdk-0.1.1.tgz","fileCount":7,"integrity":"sha512-Ybat9mgGIUoaOt2A9sqa4KClh+L0sOLwDE9ZChyRp1fBBqGv9agVnsV6A9UTCX5p24I6F24QCc26ebl+Ad5Wig==","signatures":[{"sig":"MEQCIH6MJqgobiCLT+amZw5hKF9BgsfQnE9WML6VyZLFU6Q4AiBNjwdvp/hxXCNxcwYSruXxuAzXNCQZr1ilfaMP1/jJug==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":72457},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}}},"gitHead":"2428c2da5a90b95b843b492c689d8ae6fbefda08","scripts":{"dev":"tsup --watch","build":"tsup","typecheck":"tsc --noEmit"},"_npmUser":{"name":"muyiwaloba","email":"jadcreationsltd@gmail.com"},"_npmVersion":"10.8.2","description":"Anomira browser-side fingerprinting, bot detection, and form guard SDK","directories":{},"_nodeVersion":"20.20.2","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.2","typescript":"^5.4.5"},"_npmOperationalInternal":{"tmp":"tmp/browser-sdk_0.1.1_1779209989511_0.7796829251711068","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@anomira/browser-sdk","version":"0.1.2","license":"MIT","_id":"@anomira/browser-sdk@0.1.2","maintainers":[{"name":"muyiwaloba","email":"jadcreationsltd@gmail.com"}],"dist":{"shasum":"c4455c45e02c5ca41cc0c68f7bf029d0ecf921c1","tarball":"https://registry.npmjs.org/@anomira/browser-sdk/-/browser-sdk-0.1.2.tgz","fileCount":7,"integrity":"sha512-Q7XZXh0SSpZ99aEM+uQcTGThmub994F0ammVuaYtrAzvmnAdNNHngDeM5ikVUQ8oEvn0+4jySYTlJzxeqXKD6g==","signatures":[{"sig":"MEUCIHDuoU5SLkC3LT9C3bSIBLClXoJJLO6YP6RXLXvYtPLdAiEAlppJN519FD4W4ifhnCYAEJZALitmB+OGMuBsvFtNUD0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":79953},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}}},"gitHead":"f35b4ae24eb5811f780c1186c1bfbf5ee3d79546","scripts":{"dev":"tsup --watch","build":"tsup","typecheck":"tsc --noEmit"},"_npmUser":{"name":"muyiwaloba","email":"jadcreationsltd@gmail.com"},"_npmVersion":"10.8.2","description":"Anomira browser-side fingerprinting, bot detection, and form guard SDK","directories":{},"_nodeVersion":"20.20.2","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.2","typescript":"^5.4.5"},"_npmOperationalInternal":{"tmp":"tmp/browser-sdk_0.1.2_1779222613697_0.5342198792042998","host":"s3://npm-registry-packages-npm-production"}},"0.1.3":{"name":"@anomira/browser-sdk","version":"0.1.3","description":"Anomira browser-side fingerprinting, bot detection, and form guard SDK","license":"MIT","type":"module","main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}}},"scripts":{"build":"tsup","dev":"tsup --watch","typecheck":"tsc --noEmit"},"devDependencies":{"tsup":"^8.0.2","typescript":"^5.4.5"},"gitHead":"770ec66ff44765bd3b7cdab0b229c614b5a4cf12","_id":"@anomira/browser-sdk@0.1.3","_nodeVersion":"25.9.0","_npmVersion":"11.12.1","dist":{"integrity":"sha512-YQ5R7qfDVyb+NCviwEZijCtX/3dF+lNEBBE8E84E3h733peBfGrbsY1EE4/GEFbaFxsKILZ36nrv0BfE4+vBiQ==","shasum":"d9dbb0a3aa472e1f589fab76c04c017e2eac7e32","tarball":"https://registry.npmjs.org/@anomira/browser-sdk/-/browser-sdk-0.1.3.tgz","fileCount":7,"unpackedSize":83715,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCBKcDs+er6tyqj2KmMgTxFTl/40BYiLCF3SIN9Z+PZfwIhAPA35OH6g0U9DRjjsO+ISfFTeRCwGCDV1kcMdi5DUwKa"}]},"_npmUser":{"name":"muyiwaloba","email":"jadcreationsltd@gmail.com"},"directories":{},"maintainers":[{"name":"muyiwaloba","email":"jadcreationsltd@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/browser-sdk_0.1.3_1779254783539_0.28493016813738303"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-19T16:38:53.369Z","modified":"2026-05-20T05:26:23.815Z","0.1.0":"2026-05-19T16:38:53.640Z","0.1.1":"2026-05-19T16:59:49.717Z","0.1.2":"2026-05-19T20:30:13.881Z","0.1.3":"2026-05-20T05:26:23.685Z"},"license":"MIT","description":"Anomira browser-side fingerprinting, bot detection, and form guard SDK","maintainers":[{"name":"muyiwaloba","email":"jadcreationsltd@gmail.com"}],"readme":"# @anomira/browser-sdk\n\nBrowser-side device fingerprinting, bot detection, and form behavioural analysis for [Anomira](https://anomira.io).\n\nWorks alongside `@anomira/node-sdk` on your server. The browser SDK collects signals the server cannot see — canvas rendering, WebGL GPU strings, automation flags, and form interaction timing — and passes them to your backend via a single request header. No separate API calls, no external network requests, no cookies.\n\n---\n\n## How it fits into the stack\n\n```\nBrowser                          Your Server                    Anomira\n────────                         ───────────                    ───────\n@anomira/browser-sdk             @anomira/node-sdk              ingest\n  ↓ collects signals               ↓ reads X-Anomira-FP          ↓ combines\n  ↓ builds token                   ↓ adds to event meta           ↓ server + browser\n  → X-Anomira-FP header ────────→  → sends to ingest ──────────→  scores together\n```\n\nThe server SDK middleware reads the header automatically — you do not need to change anything on your backend after upgrading to the latest `@anomira/node-sdk`.\n\n---\n\n## Installation\n\n```bash\nnpm install @anomira/browser-sdk\n```\n\nOr via CDN for server-rendered apps (Django, Laravel, Rails, plain HTML):\n\n```html\n<!-- Always latest (not recommended for production — use a pinned version) -->\n<script src=\"https://cdn.jsdelivr.net/npm/@anomira/browser-sdk/dist/index.global.js\"></script>\n\n<!-- Pinned to a specific version (recommended) -->\n<script src=\"https://cdn.jsdelivr.net/npm/@anomira/browser-sdk@0.1.1/dist/index.global.js\"></script>\n```\n\n> The CDN URL is served by [jsDelivr](https://www.jsdelivr.com/) and becomes active automatically once the package is published to npm. No separate CDN setup is required.\n\n---\n\n## Quick start\n\n### One line (auto-attach mode)\n\n```typescript\nimport { AnomaliraBrowser } from \"@anomira/browser-sdk\";\n\nAnomaliraBrowser.init().attach();\n```\n\nCall this once on page load. After this, every `fetch()` and `XMLHttpRequest` to your own origin automatically carries the `X-Anomira-FP` header. No further changes needed.\n\n---\n\n## Framework guides\n\n### Next.js (App Router)\n\nCreate a client component that initialises the SDK once on page load:\n\n```tsx\n// app/providers.tsx\n\"use client\";\nimport { useEffect } from \"react\";\nimport { AnomaliraBrowser } from \"@anomira/browser-sdk\";\n\nexport function AnomalyProvider({ children }: { children: React.ReactNode }) {\n  useEffect(() => {\n    AnomaliraBrowser.init().attach();\n  }, []);\n  return <>{children}</>;\n}\n```\n\nAdd it to your root layout:\n\n```tsx\n// app/layout.tsx\nimport { AnomalyProvider } from \"./providers\";\n\nexport default function RootLayout({ children }: { children: React.ReactNode }) {\n  return (\n    <html lang=\"en\">\n      <body>\n        <AnomalyProvider>{children}</AnomalyProvider>\n      </body>\n    </html>\n  );\n}\n```\n\n> **Works with Next.js rewrites and separate backends.** The SDK sets a first-party session cookie (`anomira_fp`) after fingerprinting. The cookie is automatically included in every request by the browser — no header injection required, no proxy configuration needed.\n\n### Next.js (Pages Router)\n\n```tsx\n// pages/_app.tsx\nimport type { AppProps } from \"next/app\";\nimport { useEffect } from \"react\";\nimport { AnomaliraBrowser } from \"@anomira/browser-sdk\";\n\nexport default function App({ Component, pageProps }: AppProps) {\n  useEffect(() => {\n    AnomaliraBrowser.init().attach();\n  }, []);\n  return <Component {...pageProps} />;\n}\n```\n\n### React (Vite / CRA)\n\n```tsx\n// src/main.tsx\nimport React from \"react\";\nimport ReactDOM from \"react-dom/client\";\nimport { AnomaliraBrowser } from \"@anomira/browser-sdk\";\nimport App from \"./App\";\n\n// Initialise before React mounts so fingerprinting runs in parallel\nAnomaliraBrowser.init().attach();\n\nReactDOM.createRoot(document.getElementById(\"root\")!).render(\n  <React.StrictMode>\n    <App />\n  </React.StrictMode>\n);\n```\n\n### Vue 3\n\n```typescript\n// src/main.ts\nimport { createApp } from \"vue\";\nimport { AnomaliraBrowser } from \"@anomira/browser-sdk\";\nimport App from \"./App.vue\";\n\nAnomaliraBrowser.init().attach();\ncreateApp(App).mount(\"#app\");\n```\n\n### Plain HTML / CDN\n\n```html\n<!DOCTYPE html>\n<html>\n  <head>...</head>\n  <body>\n    <!-- your content -->\n\n    <script src=\"https://cdn.jsdelivr.net/npm/@anomira/browser-sdk@0.1.1/dist/index.global.js\"></script>\n    <script>\n      AnomaliraBrowserSDK.AnomaliraBrowser.init().attach();\n    </script>\n  </body>\n</html>\n```\n\n---\n\n## Identity linkage — `identify()`\n\nCall `identify()` after login succeeds to link the current browser session to the authenticated user. Once called, every subsequent request carries both the device fingerprint and the userId in the browser token.\n\nThis is the connection between browser-level signals (bot score, fingerprint, form timing) and your server-side user profiles. Without it, Anomira sees \"fingerprint A7B3 made 10 failed logins\" — with it, it sees \"user john@example.com using fingerprint A7B3 made 10 failed logins from a new device.\"\n\n```tsx\n// After login succeeds\nconst res  = await fetch(\"/api/auth/login\", { method: \"POST\", ... });\nconst data = await res.json();\n\nif (res.ok) {\n  // Link this browser session to the authenticated user\n  AnomaliraBrowser.identify({ userId: data.user.id });\n}\n```\n\n```tsx\n// Or in your auth context — runs on every page load if user is already logged in\nuseEffect(() => {\n  if (user?.id) {\n    AnomaliraBrowser.identify({ userId: user.id });\n  }\n}, [user?.id]);\n```\n\nThe server SDK uses this as a fallback `userId` on routes where your auth middleware hasn't populated `req.user` yet — including the login route itself, where the user is not authenticated at the time of the request.\n\n---\n\n## Login form integration (recommended)\n\nAuto-attach mode covers all requests, but for login and registration forms you should also call `recordSubmit()` so the SDK can measure accurate time-to-submit — a strong signal for detecting automated credential stuffing.\n\n```tsx\n// components/LoginForm.tsx\nimport { AnomaliraBrowser } from \"@anomira/browser-sdk\";\n\nexport function LoginForm() {\n  async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {\n    e.preventDefault();\n\n    // Record submit time before any async work\n    AnomaliraBrowser.init().recordSubmit();\n\n    const form = new FormData(e.currentTarget);\n    await fetch(\"/api/auth/login\", {\n      method: \"POST\",\n      headers: { \"Content-Type\": \"application/json\" },\n      body: JSON.stringify({\n        email:    form.get(\"email\"),\n        password: form.get(\"password\"),\n      }),\n      // X-Anomira-FP is injected automatically by attach()\n    });\n  }\n\n  return (\n    <form onSubmit={handleSubmit}>\n      <input type=\"email\"    name=\"email\"    />\n      <input type=\"password\" name=\"password\" />\n      <button type=\"submit\">Log in</button>\n    </form>\n  );\n}\n```\n\n---\n\n## Manual mode\n\nIf you prefer explicit control over when and where the header is sent:\n\n```typescript\nimport { AnomaliraBrowser, HEADER_NAME } from \"@anomira/browser-sdk\";\n\nconst sdk = AnomaliraBrowser.init();\n\n// Wait for async fingerprinting (canvas + audio) to complete\nawait sdk.ready();\n\n// Add the header manually on specific requests\nconst response = await fetch(\"/api/auth/login\", {\n  method: \"POST\",\n  headers: {\n    \"Content-Type\": \"application/json\",\n    [HEADER_NAME]: sdk.getToken(true), // true = include form interaction signals\n  },\n  body: JSON.stringify({ email, password }),\n});\n```\n\n---\n\n## API reference\n\n### `AnomaliraBrowser.init()`\n\nReturns the singleton instance, creating it if it does not exist. Starts fingerprinting immediately in the background. Safe to call multiple times — always returns the same instance.\n\n```typescript\nconst sdk = AnomaliraBrowser.init();\n```\n\n### `sdk.attach(options?)`\n\nPatches `window.fetch` and `XMLHttpRequest` to inject `X-Anomira-FP` on every same-origin request. Returns `this` for chaining.\n\n```typescript\nsdk.attach({\n  sameOriginOnly:       true,  // default: true — only inject on same-origin requests\n  includeFormOnSubmit:  true,  // default: true — include form signals on auth endpoints\n});\n```\n\n### `sdk.ready()`\n\nReturns a `Promise<void>` that resolves when async fingerprinting (canvas rendering, audio processing) is complete. Calling `getToken()` before `ready()` resolves will produce a token with an empty fingerprint.\n\n```typescript\nawait sdk.ready();\n```\n\n### `sdk.getToken(includeForm?)`\n\nReturns a base64-encoded payload string for the `X-Anomira-FP` header.\n\n```typescript\nconst token = sdk.getToken();       // without form signals\nconst token = sdk.getToken(true);   // with form interaction signals (paste, timing)\n```\n\n### `sdk.recordSubmit()`\n\nRecords the exact timestamp of a form submission. Call this in your submit handler before any `await` statements for accurate time-to-submit measurement.\n\n```typescript\nform.addEventListener(\"submit\", () => {\n  sdk.recordSubmit();\n  // ... rest of submit handler\n});\n```\n\n### `sdk.botScore`\n\nCurrent bot confidence score (0–100). Available after `await sdk.ready()`. A score of 0 means the SDK has no evidence of automation; 95+ means a definitive automation flag was detected.\n\n```typescript\nawait sdk.ready();\nconsole.log(sdk.botScore);     // e.g. 0 (human) or 95 (webdriver detected)\nconsole.log(sdk.botSignals);   // e.g. [\"webdriver\", \"no_plugins\"]\nconsole.log(sdk.fingerprint);  // e.g. \"a7b3c2d1e4f5...\"\n```\n\n### `HEADER_NAME`\n\nThe header name constant: `\"X-Anomira-FP\"`. Use this if you need to set the header manually.\n\n```typescript\nimport { HEADER_NAME } from \"@anomira/browser-sdk\";\n// HEADER_NAME === \"X-Anomira-FP\"\n```\n\n---\n\n## What signals are collected\n\n### Device fingerprint\n| Signal | Source | Stability |\n|--------|--------|-----------|\n| Canvas rendering | GPU + driver + font renderer | High — survives incognito, restarts |\n| WebGL renderer string | GPU vendor (e.g. \"NVIDIA GeForce RTX 3070\") | Very high |\n| Audio processing | DAC + OS audio driver | High |\n| Screen dimensions + pixel ratio | Hardware | Medium |\n| User agent + language | Browser | Medium (changes on updates) |\n\n### Bot detection signals\n| Signal | What it catches | Score |\n|--------|----------------|-------|\n| `navigator.webdriver` | Selenium, Puppeteer, Playwright | 95 |\n| PhantomJS globals (`_phantom`, `callPhantom`) | PhantomJS | 95 |\n| `__nightmare` global | Nightmare.js | 95 |\n| `domAutomation` / `domAutomationController` | Chrome DevTools automation | 95 |\n| `__pw_manual` | Playwright stealth | 95 |\n| Missing `window.chrome` in Chromium UA | Headless Chrome | 75 |\n| Zero plugins in Chromium | Headless Chrome | 65 |\n| Empty `navigator.languages` | Misconfigured automation | 70 |\n| Zero-dimension screen (`0x0`) | Headless with no display | 85 |\n| Default headless screen (`800x600`) | Unmodified headless | 55 |\n| Mobile UA without touch support | UA spoofing | 50 |\n\n### Form signals (login forms)\n| Signal | What it catches |\n|--------|----------------|\n| `pasted` | Password field paste event — nearly all credential stuffing bots paste |\n| `ttf` | Time from page load to first field interaction (bots act in < 200ms) |\n| `tts` | Time from first interaction to submission (bots submit in < 500ms) |\n\n---\n\n## How the server uses these signals\n\nYou do not need to read the header yourself. After upgrading to the latest `@anomira/node-sdk`, the Express and Fastify middleware automatically:\n\n1. Reads `X-Anomira-FP` from the incoming request\n2. Decodes the payload and adds `_bfp`, `_bbot`, `_bsigs`, `_bpaste`, `_bttf`, `_btts` to the event meta\n3. Takes the higher of the server-side HTTP fingerprint score and the browser-side bot score as the effective score\n4. Flags paste events on login endpoints as credential stuffing candidates\n\nThe Anomira dashboard will show browser-confirmed signals alongside server-side signals, improving detection accuracy — particularly for headless Chrome bots that can spoof HTTP headers but cannot easily fake canvas rendering or hide `navigator.webdriver`.\n\n---\n\n## Verifying the integration\n\nOpen your browser DevTools → Network tab → click any API request to your backend → Request Headers. You should see:\n\n```\nX-Anomira-FP: eyJ2IjoxLCJmcCI6ImE3YjNjMmQxZTRmNS4uLiIsImJvdCI6MCwic2lncyI6W10s...\n```\n\nYou can decode it in the browser console to inspect the payload:\n\n```javascript\nJSON.parse(atob(/* paste the header value here */))\n// {v: 1, fp: \"a7b3c2d1...\", bot: 0, sigs: [], tz: \"Africa/Lagos\", ...}\n```\n\n---\n\n## Accuracy and limitations\n\nClient-side-only fingerprinting achieves approximately 40–60% device uniqueness (source: FingerprintJS research). Anomira combines the browser fingerprint with server-side signals (TLS JA3/JA4, H2 SETTINGS frames, IP reputation, geo-velocity) to reach significantly higher combined accuracy.\n\n**What the browser SDK catches well:**\n- Puppeteer and Playwright without stealth plugins (~95% of automated attacks)\n- Selenium WebDriver\n- PhantomJS / Nightmare.js\n- Headless Chrome with default configuration\n- Credential stuffing bots that paste credentials\n\n**What it does not catch:**\n- Puppeteer with stealth plugins and correct chrome globals\n- Real-device bot farms (humans solving CAPTCHAs, real devices)\n- Attacks that do not go through the browser (direct API calls bypass the header entirely)\n\nFor direct API attacks (no browser), the server-side SDK's IP reputation, rate limiting, and behavioural baseline detection remains the primary defence.\n\n---\n\n## Privacy and compliance\n\nThe browser SDK collects device characteristics for **fraud prevention and security purposes**. This qualifies as legitimate interest under:\n- **GDPR** Article 6(1)(f) — no consent banner required for security-purpose fingerprinting\n- **NDPA 2023** — security processing is permitted without explicit consent; disclosure is required\n\n**What you must do:** Update your privacy policy to disclose that device attributes are collected for security and fraud prevention. A single sentence is sufficient:\n\n> *\"We collect technical device attributes (browser rendering characteristics, hardware identifiers) for the purpose of fraud detection and account security.\"*\n\nNo cookie banner or explicit consent toggle is required for security-purpose fingerprinting.\n\n**What the SDK does NOT do:**\n- Cross-site tracking\n- Advertising or analytics profiling\n- Persistent storage (no cookies, no localStorage)\n- External network requests (all data goes to your own server)\n\n---\n\n## Troubleshooting\n\n**Header not appearing in requests**\n\nMake sure `attach()` is called before any requests are made, and that you are not using a service worker that intercepts fetch before the SDK can patch it.\n\n**Bot score is 0 but the attacker is automated**\n\nThe SDK only detects automation artifacts that bots leave in the browser environment. Sophisticated bots using stealth plugins can clear most of these signals. The server-side SDK's rate limiting and IP reputation will still catch most of these.\n\n**TypeScript errors about `deviceMemory`**\n\n`navigator.deviceMemory` is not in the standard TypeScript DOM types. The SDK handles this internally. If you are accessing `sdk.fingerprint` directly, cast `navigator` to `unknown` first.\n\n**SDK not initialising in SSR environments**\n\nThe browser SDK must only run in the browser. In Next.js, always call `AnomaliraBrowser.init()` inside a `useEffect` hook or in a `\"use client\"` component. Calling it during server-side rendering will throw because `document` and `window` are not available.\n\n---\n\n## Changelog\n\n### 0.1.0\n- Initial release\n- Canvas, WebGL, and audio device fingerprinting\n- Three-tier bot detection (webdriver, headless Chrome, automation globals)\n- Form guard: paste detection and interaction timing\n- Auto-attach mode for fetch and XMLHttpRequest\n- Express and Fastify server SDK integration via `X-Anomira-FP` header\n","readmeFilename":"README.md"}