Package Exports
- @tn3w/openage
Readme
Privacy-first age verification for the web
OpenAge runs face tracking, liveness checks, and age estimation on-device. Use it as a drop-in age gate with a checkbox-style widget, modal flow, or button binding.
At a Glance
| Browser-side | Server-backed | UI |
|---|---|---|
| On-device face analysis | Optional WASM verification | Widget popup, inline embed, bind |
| No raw camera upload | Signed sessions and tokens | Normal, compact, invisible |
| Serverless soft gates | Hosted or custom backend | Auto, light, dark |
Install
npm install @tn3w/openage<script src="https://cdn.jsdelivr.net/npm/@tn3w/openage/dist/openage.min.js"></script>Quick Start
CDN
<div class="openage" data-sitekey="ag_live_xxxx" data-callback="onVerified"></div>
<script src="https://cdn.jsdelivr.net/npm/@tn3w/openage/dist/openage.min.js"></script>
<script>
function onVerified(token) {
console.log('verified', token);
}
</script>npm
import OpenAge from '@tn3w/openage';
OpenAge.render('#gate', {
mode: 'serverless',
layout: 'inline',
minAge: 18,
callback: (token) => console.log(token),
errorCallback: (error) => console.error(error),
});Inline Embed
OpenAge.render('#gate', {
mode: 'serverless',
layout: 'inline',
minAge: 18,
});layout: 'inline' removes the checkbox shell and renders the verification
panel directly in the container. The first verification step starts
immediately after loading.
Bound Flow
OpenAge.bind('#buy-btn', {
sitekey: 'ag_live_xxxx',
callback: (token) => submitForm(token),
});Modes
| Mode | Backend | Use case |
|---|---|---|
serverless |
none | client-only soft gates |
sitekey |
OpenAge hosted | production verification |
custom |
your server | self-hosted verification |
serverless keeps everything local and returns a client-signed token.
sitekey and custom use a server session and a WASM VM for stronger checks.
Core API
OpenAge.render(container, params);
OpenAge.open(params);
OpenAge.bind(element, params);
OpenAge.reset(widgetId);
OpenAge.remove(widgetId);
OpenAge.getToken(widgetId);
OpenAge.execute(widgetId);
await OpenAge.challenge(params);Runtime errors keep the popup open long enough to explain what happened. If no camera is available, OpenAge tells the user to plug one in and closes the popup automatically after 5 seconds.
Main Params
| Param | Values |
|---|---|
mode |
serverless, sitekey, custom |
layout |
widget, inline |
theme |
light, dark, auto |
size |
normal, compact, invisible |
minAge |
number, default 18 |
sitekey |
required for hosted mode |
server |
required for custom mode |
Demo
- Static demo: https://tn3w.github.io/OpenAge/
- Local server demo:
cd server
pip install -r requirements.txt
python server.pyThe repository also includes demo/, a minimal GitHub Pages build that loads
the jsDelivr bundle for @tn3w/openage in inline embedded serverless mode.
Development
npm install
npm test
npm run build
npm run devOptional server:
cd server
pip install -r requirements.txt
python server.pyFormatting
pip install black isort
isort . && black .
npx prtfm
clang-format -i server/wasm/src/*.c server/wasm/src/*.h