npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@a3api/signals

v0.1.2

Published

Browser signal collector for A3 Age Assurance API

Downloads

337

Readme

@a3api/signals

Drop-in browser SDK for the Arcadia Age API (A3). 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.

  • < 5 KB gzipped
  • Zero runtime dependencies
  • Zero PII — no raw text, no images, no user content
  • Vanilla JS, React, and Vue entrypoints
  • ESM + CJS + full TypeScript types

Why?

California 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.

The SDK does not make API calls. You forward the collected signals from your backend, where your API key stays secure.

Install

npm install @a3api/signals

Quick Start

Vanilla JS

import { createSignalCollector } from '@a3api/signals';

const collector = createSignalCollector();

// ... user interacts with your page ...

const signals = collector.getSignals();
// signals = { behavioral_metrics, input_complexity, device_context, contextual_signals }

// Send signals to your backend, which forwards them to A3
await fetch('/api/assess-age', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    os_signal: 'not-available',
    user_country_code: 'US',
    ...signals,
  }),
});

// Clean up when done
collector.destroy();

React

import { useSignalCollector } from '@a3api/signals/react';

function AgeGate() {
  const { getSignals, isReady } = useSignalCollector();

  const handleSubmit = async () => {
    const signals = getSignals();
    if (!signals) return;

    await fetch('/api/assess-age', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        os_signal: 'not-available',
        user_country_code: 'US',
        ...signals,
      }),
    });
  };

  return <button onClick={handleSubmit} disabled={!isReady}>Continue</button>;
}

Vue

<script setup>
import { useSignalCollector } from '@a3api/signals/vue';

const { getSignals, isReady } = useSignalCollector();

async function handleSubmit() {
  const signals = getSignals();
  if (!signals) return;

  await fetch('/api/assess-age', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      os_signal: 'not-available',
      user_country_code: 'US',
      ...signals,
    }),
  });
}
</script>

<template>
  <button @click="handleSubmit" :disabled="!isReady">Continue</button>
</template>

What It Collects

| Category | Signals | Source | |----------|---------|--------| | Behavioral Metrics | Touch precision, scroll velocity, form completion time, autofill detection, pressure variance, multi-touch frequency | PointerEvent, WheelEvent, FocusEvent, AnimationEvent | | Input Complexity | Autocorrect rate, word complexity score | InputEvent | | Device Context | OS version, device model, high contrast mode, screen scale factor | navigator.userAgentData / userAgent, matchMedia, devicePixelRatio | | Contextual Signals | Referrer category, timezone offset | document.referrer, Date.getTimezoneOffset() |

All collection is passive — no prompts, no camera, no popups. The SDK listens to events the user is already generating.

Backend Integration

The 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:

// Express.js example
app.post('/api/assess-age', async (req, res) => {
  const response = await fetch('https://api.a3api.io/v1/assurance/assess-age', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.A3_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      os_signal: 'not-available',
      user_country_code: 'US',
      ...req.body, // signals from the SDK
    }),
  });

  const result = await response.json();
  // Store result.verification_token in your audit log
  res.json({ verdict: result.verdict, bracket: result.assessed_age_bracket });
});

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.

API

createSignalCollector(options?)

Creates a new signal collector instance.

| Option | Type | Default | Description | |--------|------|---------|-------------| | root | Document \| HTMLElement | document | Root element for event listeners |

Returns a SignalCollector with:

  • getSignals() — returns the current SignalPayload
  • isReadytrue until destroy() is called
  • destroy() — removes all event listeners

useSignalCollector() (React / Vue)

Framework binding that manages the collector lifecycle automatically.

Returns { getSignals, isReady } where getSignals() returns SignalPayload | null.

Links

License

MIT