{"_id":"@a3api/signals","_rev":"3-46982b40e5849075f2b172c6de1d76fd","name":"@a3api/signals","dist-tags":{"latest":"0.1.2"},"versions":{"0.1.0":{"name":"@a3api/signals","version":"0.1.0","_id":"@a3api/signals@0.1.0","maintainers":[{"name":"digital_arcadia","email":"connect@digital-arcadia.com"}],"dist":{"shasum":"cb2d19efd5edc21f0aaca46b5af257388cb7b0ce","tarball":"https://registry.npmjs.org/@a3api/signals/-/signals-0.1.0.tgz","fileCount":15,"integrity":"sha512-uAIVwF+G/PHWVicwgLVy6J6gUoLI6mTmQRVO+FBgohnI6Fl8DLa/9c8hmu1ar9hUnER8DrkQcam/Vos50zEIKw==","signatures":[{"sig":"MEUCICMvHlKvtSe3WT4jBVVAr+jYRGv7X17NFGdObATati3EAiEAwYgcEj7lR1+UOizi2/BSoLzmE0Gk1sg6xuYacPMzo6g=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":87677},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./vue":{"types":"./dist/vue/index.d.ts","import":"./dist/vue/index.js","require":"./dist/vue/index.cjs"},"./react":{"types":"./dist/react/index.d.ts","import":"./dist/react/index.js","require":"./dist/react/index.cjs"}},"gitHead":"673719537ac96c53a89c5bc38b955ca86b9965a3","private":false,"scripts":{"test":"vitest run","build":"tsup","test:watch":"vitest"},"_npmUser":{"name":"digital_arcadia","email":"connect@digital-arcadia.com"},"_npmVersion":"10.8.2","description":"Browser signal collector for A3 Age Assurance API","directories":{},"sideEffects":false,"_nodeVersion":"20.10.0","_hasShrinkwrap":false,"devDependencies":{"vue":"^3.0.0","tsup":"^8.0.0","jsdom":"^25.0.0","react":"^18.0.0","vitest":"^2.0.0","typescript":"^5.7.2","@types/react":"^18.0.0"},"peerDependencies":{"vue":">=3","react":">=17"},"peerDependenciesMeta":{"vue":{"optional":true},"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/signals_0.1.0_1772299671883_0.5402682797612532","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@a3api/signals","version":"0.1.1","_id":"@a3api/signals@0.1.1","maintainers":[{"name":"digital_arcadia","email":"connect@digital-arcadia.com"}],"dist":{"shasum":"44a16a9240932e048dddebd75a39e364cf5b398f","tarball":"https://registry.npmjs.org/@a3api/signals/-/signals-0.1.1.tgz","fileCount":16,"integrity":"sha512-s5L7GxsEe81qBLMKtFpTdCDqvoF7V3nEvHwe36bt8Txt4snyVOtb3xCj6yOQSMvW8eOgS/McLRYnYaOmOgnWZA==","signatures":[{"sig":"MEUCIFsDYLXDdMkItbPchaBYJ2yAba4gFgPT+4AGAeTG7FsrAiEApb8dFniBBUeKIgxTJ4G/AlKKFWcvVPoOzwoMc7TlP8g=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":93380},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./vue":{"types":"./dist/vue/index.d.ts","import":"./dist/vue/index.js","require":"./dist/vue/index.cjs"},"./react":{"types":"./dist/react/index.d.ts","import":"./dist/react/index.js","require":"./dist/react/index.cjs"}},"gitHead":"673719537ac96c53a89c5bc38b955ca86b9965a3","private":false,"scripts":{"test":"vitest run","build":"tsup","test:watch":"vitest"},"_npmUser":{"name":"digital_arcadia","email":"connect@digital-arcadia.com"},"_npmVersion":"10.8.2","description":"Browser signal collector for A3 Age Assurance API","directories":{},"sideEffects":false,"_nodeVersion":"20.10.0","_hasShrinkwrap":false,"devDependencies":{"vue":"^3.0.0","tsup":"^8.0.0","jsdom":"^25.0.0","react":"^18.0.0","vitest":"^2.0.0","typescript":"^5.7.2","@types/react":"^18.0.0"},"peerDependencies":{"vue":">=3","react":">=17"},"peerDependenciesMeta":{"vue":{"optional":true},"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/signals_0.1.1_1772299838208_0.8619803801316062","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@a3api/signals","version":"0.1.2","description":"Browser signal collector for A3 Age Assurance API","private":false,"type":"module","main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./react":{"types":"./dist/react/index.d.ts","import":"./dist/react/index.js","require":"./dist/react/index.cjs"},"./vue":{"types":"./dist/vue/index.d.ts","import":"./dist/vue/index.js","require":"./dist/vue/index.cjs"}},"sideEffects":false,"scripts":{"build":"tsup","test":"vitest run","test:watch":"vitest"},"peerDependencies":{"react":">=17","vue":">=3"},"peerDependenciesMeta":{"react":{"optional":true},"vue":{"optional":true}},"devDependencies":{"@types/react":"^18.0.0","jsdom":"^25.0.0","react":"^18.0.0","tsup":"^8.0.0","typescript":"^5.7.2","vitest":"^2.0.0","vue":"^3.0.0"},"_id":"@a3api/signals@0.1.2","gitHead":"9157abd8eb48a85a780bf768728f0866c4c987c5","_nodeVersion":"20.10.0","_npmVersion":"10.8.2","dist":{"integrity":"sha512-I++3/3i6JKuL1NgMqDhJbb5FGwWgd564lRiMMDVWvuSolZiccpM6PsoLSW0H058b/KuzMgnd8NOi5xfQTGuA0Q==","shasum":"fea566c4de6e100ec633b8a428554a022fcce42c","tarball":"https://registry.npmjs.org/@a3api/signals/-/signals-0.1.2.tgz","fileCount":16,"unpackedSize":93370,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIEH0aFqMDKioFsHAxxc+pBjiMgriwETDZA6/Yue8u0oDAiEAqMdu4McfasKMo1LNsfw2UzNwkMy4IAFWFUCBCrYnsQg="}]},"_npmUser":{"name":"digital_arcadia","email":"connect@digital-arcadia.com"},"directories":{},"maintainers":[{"name":"digital_arcadia","email":"connect@digital-arcadia.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/signals_0.1.2_1772332391511_0.46637427385975383"},"_hasShrinkwrap":false}},"time":{"created":"2026-02-28T17:27:51.824Z","modified":"2026-03-01T02:33:11.785Z","0.1.0":"2026-02-28T17:27:52.031Z","0.1.1":"2026-02-28T17:30:38.382Z","0.1.2":"2026-03-01T02:33:11.653Z"},"description":"Browser signal collector for A3 Age Assurance API","maintainers":[{"name":"digital_arcadia","email":"connect@digital-arcadia.com"}],"readme":"# @a3api/signals\r\n\r\nDrop-in browser SDK for the [Arcadia Age API (A3)](https://www.a3api.io). Passively collects behavioral and contextual signals from standard browser events and packages them into the exact payload shape expected by `POST /v1/assurance/assess-age`.\r\n\r\n- **< 5 KB** gzipped\r\n- **Zero runtime dependencies**\r\n- **Zero PII** — no raw text, no images, no user content\r\n- Vanilla JS, React, and Vue entrypoints\r\n- ESM + CJS + full TypeScript types\r\n\r\n## Why?\r\n\r\nCalifornia AB 1043 mandates age assurance, but browsers have no OS-level age signal. This SDK fills that gap by collecting touch precision, scroll velocity, form timing, input complexity, device context, and referrer data — the signals A3's fusion engine needs to produce a verdict on the web.\r\n\r\nThe SDK **does not** make API calls. You forward the collected signals from your backend, where your API key stays secure.\r\n\r\n## Install\r\n\r\n```bash\r\nnpm install @a3api/signals\r\n```\r\n\r\n## Quick Start\r\n\r\n### Vanilla JS\r\n\r\n```typescript\r\nimport { createSignalCollector } from '@a3api/signals';\r\n\r\nconst collector = createSignalCollector();\r\n\r\n// ... user interacts with your page ...\r\n\r\nconst signals = collector.getSignals();\r\n// signals = { behavioral_metrics, input_complexity, device_context, contextual_signals }\r\n\r\n// Send signals to your backend, which forwards them to A3\r\nawait fetch('/api/assess-age', {\r\n  method: 'POST',\r\n  headers: { 'Content-Type': 'application/json' },\r\n  body: JSON.stringify({\r\n    os_signal: 'not-available',\r\n    user_country_code: 'US',\r\n    ...signals,\r\n  }),\r\n});\r\n\r\n// Clean up when done\r\ncollector.destroy();\r\n```\r\n\r\n### React\r\n\r\n```tsx\r\nimport { useSignalCollector } from '@a3api/signals/react';\r\n\r\nfunction AgeGate() {\r\n  const { getSignals, isReady } = useSignalCollector();\r\n\r\n  const handleSubmit = async () => {\r\n    const signals = getSignals();\r\n    if (!signals) return;\r\n\r\n    await fetch('/api/assess-age', {\r\n      method: 'POST',\r\n      headers: { 'Content-Type': 'application/json' },\r\n      body: JSON.stringify({\r\n        os_signal: 'not-available',\r\n        user_country_code: 'US',\r\n        ...signals,\r\n      }),\r\n    });\r\n  };\r\n\r\n  return <button onClick={handleSubmit} disabled={!isReady}>Continue</button>;\r\n}\r\n```\r\n\r\n### Vue\r\n\r\n```vue\r\n<script setup>\r\nimport { useSignalCollector } from '@a3api/signals/vue';\r\n\r\nconst { getSignals, isReady } = useSignalCollector();\r\n\r\nasync function handleSubmit() {\r\n  const signals = getSignals();\r\n  if (!signals) return;\r\n\r\n  await fetch('/api/assess-age', {\r\n    method: 'POST',\r\n    headers: { 'Content-Type': 'application/json' },\r\n    body: JSON.stringify({\r\n      os_signal: 'not-available',\r\n      user_country_code: 'US',\r\n      ...signals,\r\n    }),\r\n  });\r\n}\r\n</script>\r\n\r\n<template>\r\n  <button @click=\"handleSubmit\" :disabled=\"!isReady\">Continue</button>\r\n</template>\r\n```\r\n\r\n## What It Collects\r\n\r\n| Category | Signals | Source |\r\n|----------|---------|--------|\r\n| **Behavioral Metrics** | Touch precision, scroll velocity, form completion time, autofill detection, pressure variance, multi-touch frequency | PointerEvent, WheelEvent, FocusEvent, AnimationEvent |\r\n| **Input Complexity** | Autocorrect rate, word complexity score | InputEvent |\r\n| **Device Context** | OS version, device model, high contrast mode, screen scale factor | navigator.userAgentData / userAgent, matchMedia, devicePixelRatio |\r\n| **Contextual Signals** | Referrer category, timezone offset | document.referrer, Date.getTimezoneOffset() |\r\n\r\nAll collection is **passive** — no prompts, no camera, no popups. The SDK listens to events the user is already generating.\r\n\r\n## Backend Integration\r\n\r\nThe SDK output matches the supplementary signal fields of `POST /v1/assurance/assess-age`. Your backend adds `os_signal` and `user_country_code`, then forwards to A3:\r\n\r\n```typescript\r\n// Express.js example\r\napp.post('/api/assess-age', async (req, res) => {\r\n  const response = await fetch('https://api.a3api.io/v1/assurance/assess-age', {\r\n    method: 'POST',\r\n    headers: {\r\n      'Authorization': `Bearer ${process.env.A3_API_KEY}`,\r\n      'Content-Type': 'application/json',\r\n    },\r\n    body: JSON.stringify({\r\n      os_signal: 'not-available',\r\n      user_country_code: 'US',\r\n      ...req.body, // signals from the SDK\r\n    }),\r\n  });\r\n\r\n  const result = await response.json();\r\n  // Store result.verification_token in your audit log\r\n  res.json({ verdict: result.verdict, bracket: result.assessed_age_bracket });\r\n});\r\n```\r\n\r\n> **Never call the A3 API from the browser.** Your API key must stay on your server. The SDK collects signals; your backend forwards them to A3.\r\n\r\n## API\r\n\r\n### `createSignalCollector(options?)`\r\n\r\nCreates a new signal collector instance.\r\n\r\n| Option | Type | Default | Description |\r\n|--------|------|---------|-------------|\r\n| `root` | `Document \\| HTMLElement` | `document` | Root element for event listeners |\r\n\r\nReturns a `SignalCollector` with:\r\n\r\n- **`getSignals()`** — returns the current `SignalPayload`\r\n- **`isReady`** — `true` until `destroy()` is called\r\n- **`destroy()`** — removes all event listeners\r\n\r\n### `useSignalCollector()` (React / Vue)\r\n\r\nFramework binding that manages the collector lifecycle automatically.\r\n\r\nReturns `{ getSignals, isReady }` where `getSignals()` returns `SignalPayload | null`.\r\n\r\n## Links\r\n\r\n- [Documentation](https://www.a3api.io/docs)\r\n- [Browser SDK Guide](https://www.a3api.io/docs/integration/sdks#browser-sdk-a3apisignals)\r\n- [API Playground](https://www.a3api.io/playground)\r\n- [Get an API Key](https://portal.a3api.io)\r\n- [AB 1043 Compliance Guide](https://www.a3api.io/docs/compliance/ab-1043)\r\n\r\n## License\r\n\r\nMIT\r\n","readmeFilename":"README.md"}