{"_id":"@apptrackx/sdk-web","name":"@apptrackx/sdk-web","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@apptrackx/sdk-web","version":"0.1.0","description":"AppTrackX Web SDK — install attribution, page views and event tracking for web properties","type":"module","main":"./dist/index.js","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"sideEffects":false,"scripts":{"build":"tsup && node scripts/size-budget.mjs","typecheck":"tsc --noEmit","lint":"eslint src","test":"vitest run","prepublishOnly":"pnpm run build"},"keywords":["analytics","attribution","mmp","apptrackx","tracking","deep-linking"],"license":"Apache-2.0","homepage":"https://github.com/mukulkanojia/AppTrackx/tree/main/sdks/web#readme","repository":{"type":"git","url":"git+https://github.com/mukulkanojia/AppTrackx.git","directory":"sdks/web"},"bugs":{"url":"https://github.com/mukulkanojia/AppTrackx/issues"},"unpkg":"./dist/index.global.js","jsdelivr":"./dist/index.global.js","publishConfig":{"access":"public"},"author":{"name":"AppTrackX"},"gitHead":"97fbc045154cc323c9c94148f8a3e8c23b4b0259","_id":"@apptrackx/sdk-web@0.1.0","_nodeVersion":"24.19.0","_npmVersion":"11.17.0","dist":{"integrity":"sha512-DBJQo08qsGOk4IwhvyMZN1pIyq7YFSw6vZ/V6Gu2QK60C6TW2VHlajVknBCkyV5ITtGnG4s8yX1F8wB0Yc5Obg==","shasum":"5e3096440eb7f121c49e14ddf4e5f4dfea9a82ce","tarball":"https://registry.npmjs.org/@apptrackx/sdk-web/-/sdk-web-0.1.0.tgz","fileCount":11,"unpackedSize":164183,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIFQaToayiwfBTCdle9YC0FZCEVcLf4uCtJKH36+uiaBHAiEA9GLpTCCgYMMSzlQ2IVJmfN6erU3VzTF1pfDzKbliB8A="}]},"_npmUser":{"name":"apptrackx","email":"Dhanmore122@gmail.com"},"directories":{},"maintainers":[{"name":"apptrackx","email":"Dhanmore122@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/sdk-web_0.1.0_1786986082160_0.019844498635052332"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-17T17:01:21.943Z","0.1.0":"2026-08-17T17:01:22.321Z","modified":"2026-08-17T17:01:22.528Z"},"maintainers":[{"name":"apptrackx","email":"Dhanmore122@gmail.com"}],"description":"AppTrackX Web SDK — install attribution, page views and event tracking for web properties","homepage":"https://github.com/mukulkanojia/AppTrackx/tree/main/sdks/web#readme","keywords":["analytics","attribution","mmp","apptrackx","tracking","deep-linking"],"repository":{"type":"git","url":"git+https://github.com/mukulkanojia/AppTrackx.git","directory":"sdks/web"},"author":{"name":"AppTrackX"},"bugs":{"url":"https://github.com/mukulkanojia/AppTrackx/issues"},"license":"Apache-2.0","readme":"# AppTrackX Web SDK\n\nPage view and event tracking for web properties. TypeScript, no dependencies,\n**2.1 KB gzipped** against a 10 KB budget the build enforces.\n\n`P2.SDKW`, built in W4-41.\n\n---\n\n## What is different about the browser\n\nWorth reading before integrating, because two of these change what you can\nexpect from the data.\n\n**It cannot sign its events.** Every native SDK signs its payload with the app's\ningest secret. A browser bundle cannot hold a secret — shipping it publishes it\nto anyone who opens DevTools — and `navigator.sendBeacon` cannot set a header at\nall. So the collector authenticates browser events by `Origin`, checked against\nthe domains registered on the app.\n\n**That is a weaker guarantee, and the data says so.** A browser sets `Origin` and\npage script cannot forge it, so this stops a rogue _website_ spending your\nbudget. It does not stop `curl`. Every browser event is therefore stored as\n`ingest_source = 'browser'` with the origin that admitted it, so a billing\nquestion can separate what was proven from what was merely plausible.\n\n**There is no device identifier.** No GAID, no IDFA, nothing the browser will\ntell you. The visitor id this SDK mints and stores in `localStorage` is the\nentire basis for saying two page views came from one person.\n\n---\n\n## Install\n\n### npm\n\n```bash\nnpm install @apptrackx/sdk-web\n```\n\n```ts\nimport { init, track, setConsent } from '@apptrackx/sdk-web'\n\ninit({ appToken: 'your-app-id', endpoint: 'https://go.apptrackx.com' })\n```\n\n### A script tag\n\nThe bundle is 2.3 KB gzipped and needs no build step. Pin the version — `latest`\nmeans a page can change behaviour without anybody deploying.\n\n```html\n<script src=\"https://unpkg.com/@apptrackx/sdk-web@0.1.0/dist/index.global.js\"></script>\n<script>\n  AppTrackX.init({\n    appToken: 'your-app-id',\n    endpoint: 'https://go.apptrackx.com',\n  })\n</script>\n```\n\n### Self-hosted\n\nServing it yourself avoids a third-party origin on every page load, which some\nconsent regimes and CSP policies require.\n\n```bash\npnpm --filter @apptrackx/sdk-web build\n# copy sdks/web/dist/index.global.js onto your own site\n```\n\n## Versioning\n\n`0.x`, and that is not modesty — the minor version may break. Pin an exact\nversion in production and read the changelog before moving.\n\n````\n\n---\n\n## Register your domain first\n\nUnder **Apps → Platforms → Web**, register the exact origin the site is served\nfrom. An unregistered origin is refused with a 401 and the events are lost.\n\n`https://shop.example.com` and `http://shop.example.com` are **different\norigins**, and so is `https://www.shop.example.com`. Register each one you\nactually serve from.\n\n---\n\n## Consent\n\nNothing is sent and nothing is stored until you say so.\n\n```ts\nbanner.onAccept(() => AppTrackX.setConsent(true))\nbanner.onReject(() => AppTrackX.setConsent(false))\n````\n\nEvents raised while the banner is unanswered are held **in memory** — never in\n`localStorage`, because writing behavioural data about someone who has not agreed\nto it is the thing the gate exists to prevent. They are delivered if consent is\ngranted and discarded if it is refused. A page closed before an answer loses\nthem, which is the correct trade.\n\nUp to 50 events are held; beyond that the oldest are dropped.\n\nIf consent is already established by other means, start open:\n\n```ts\nAppTrackX.init({ appToken: '…', endpoint: '…', consent: 'granted' })\n```\n\nWithdrawing consent stops future events. It cannot recall what was already sent,\nand this SDK does not pretend otherwise.\n\n---\n\n## Tracking\n\nA page view is sent on `init` unless you pass `autoPageView: false`.\n\n**Single-page apps must call `trackPageView()` on each route change** — no\nnavigation happens, so nothing else will.\n\n```ts\nAppTrackX.trackPageView()\n\nAppTrackX.track({\n  token: 'purchase',\n  revenue: '249.50',\n  currency: 'INR',\n  params: { plan: 'pro' },\n})\n```\n\n`revenue` is a **string**. The column is `NUMERIC(18,6)`; a JavaScript number\ncannot hold `19.99` exactly, and a revenue figure that drifts by a hundredth is\none nobody can reconcile against a payment processor.\n\n---\n\n## Carrying a visitor across your domains\n\n`localStorage` is per-origin, so a visitor moving from `example.com` to\n`shop.example.net` is two visitors with two ids. Third-party cookies used to\nsolve this and browsers have removed them; what is left is putting the id in the\nlink.\n\n```ts\nAppTrackX.init({\n  appToken: '…',\n  endpoint: '…',\n  stitchDomains: ['shop.example.net'],\n})\n\nlink.href = AppTrackX.decorateUrl(link.href)\n```\n\nOnly listed domains are decorated. Attaching the id to every outbound link would\npublish a first-party identifier to every site a visitor clicks through to,\nwhich turns it into something third parties can correlate on — so there is no\n\"decorate everything\" option.\n\nThe destination adopts the id on `init` automatically.\n\n---\n\n## API\n\n|                         |                                                                      |\n| ----------------------- | -------------------------------------------------------------------- |\n| `init(options)`         | Configure and start. Sends a page view unless `autoPageView: false`. |\n| `setConsent(granted)`   | Grant or refuse. Granting flushes anything buffered.                 |\n| `trackPageView(extra?)` | A page view, with optional extra params.                             |\n| `track(event)`          | `{ token, revenue?, currency?, params? }`.                           |\n| `decorateUrl(url)`      | The URL with the visitor id, if it points at a listed domain.        |\n| `getVisitorId()`        | The current id, or null before `init`.                               |\n\n### `InitOptions`\n\n|                 |                                                          |\n| --------------- | -------------------------------------------------------- |\n| `appToken`      | The app id. Public — it is in the page source by design. |\n| `endpoint`      | The collector, e.g. `https://go.apptrackx.com`.          |\n| `consent`       | `'pending'` (default), `'granted'` or `'denied'`.        |\n| `stitchDomains` | Domains the visitor id may travel to. Empty by default.  |\n| `autoPageView`  | Send a page view on init. `true` by default.             |\n\n---\n\n## Delivery\n\n`navigator.sendBeacon` first, falling back to `XMLHttpRequest`.\n\nThe beacon matters most in the case that is hardest to observe: a page being\nunloaded cancels its in-flight XHRs, so the last event of a session — often the\none that completes a funnel — is the most likely to be lost. A beacon is handed\nto the browser and survives the page.\n\nIt is not sufficient alone. `sendBeacon` returns `false` rather than throwing\nwhen the browser declines to queue — the per-origin budget is exhausted, or the\npayload is too large — so the SDK checks the return value and falls back. Bodies\nover 60 KB skip the beacon entirely.\n\nRequests are sent with `application/json`, which makes them non-simple and\ntherefore preflighted. That is deliberate: the preflight is where the collector\nchecks your origin.\n\n---\n\n## Things this does not do\n\n- **No automatic link decoration.** A global click handler on someone else's\n  page is a surprise, and single-page apps re-render links constantly.\n- **No retry or offline queue.** A failed delivery is lost. The native SDKs\n  persist a queue to disk; doing the same here would mean writing behavioural\n  data to `localStorage`, which the consent model rules out.\n- **No web-to-app attribution.** `P2.ATTR.08` and `P2.ATTR.09` are unbuilt, so a\n  visitor moving from your site into your app is not yet joined up.\n\n---\n\n## Development\n\n```bash\npnpm --filter @apptrackx/sdk-web test     # 57 unit tests\npnpm --filter @apptrackx/sdk-web build    # esm, cjs, iife + the size budget\n```\n\nThe build fails if the iife bundle exceeds 10 KB gzipped. That budget is\nenforced rather than documented for the reason the Android SDK's 350 KB one is:\nno single commit adds 10 KB, and by the time anyone measures, getting back under\nit is a rewrite rather than a revert.\n\n**There is no DOM test tooling in this repository** — no jsdom, no happy-dom. So\nevery decision lives in a pure module with its own tests (`identity`, `consent`,\n`payload`, `transport`, `stitch`), and `index.ts` is only the wiring that reads\nbrowser globals. Anything that can only be proven in a real browser is proven by\nrunning the built bundle in one, which is how the `sendBeacon` credentials bug\nwas found.\n","readmeFilename":"README.md","_rev":"1-a97ee3ec7dc3099d16ffcf3eeb380459"}