@tillstack/tillgate
v0.3.0
Published
TillGate browser widget — the client half of a privacy-first human check. Solves a proof-of-work challenge in a Web Worker and mints a short-lived pass token for your forms.
Maintainers
Readme
@tillstack/tillgate
The browser widget for TillGate — a privacy-first human check. It fetches a
signed proof-of-work challenge from TillDev, solves it off
the main thread in a Web Worker, exchanges the solution for a short-lived pass
token, and drops that token into a hidden tillgate-response field in your form.
Your server then verifies the token with the TillGate siteverify endpoint.
- No dependencies. One
<script>tag, no bundler required. - Non-blocking. The proof of work runs in a Web Worker (inline Blob), so the page never freezes.
- Isolated. The UI renders in a Shadow DOM — it cannot be styled by, or leak styles into, the host page.
- Self-refreshing. The pass token is transparently refreshed ~10s before it expires, so even a slow form submit carries a fresh token.
Drop-in (<script>)
<form action="/signup" method="POST">
<!-- …your fields… -->
<div class="tillgate" data-sitekey="tg_site_xxxxxxxxxxxx"></div>
<button type="submit">Sign up</button>
</form>
<script src="https://tilldev.dev/tillgate.js" async defer></script>Every .tillgate element on the page is rendered automatically on load. On
success a hidden <input name="tillgate-response"> is added to the enclosing
<form> — submit it and verify server-side.
Data attributes
| Attribute | Default | Description |
| --- | --- | --- |
| data-sitekey | — | Required. Your tg_site_… key. |
| data-api | https://tilldev.dev | API origin. |
| data-theme | auto | auto | light | dark. |
| data-callback | — | Name of a global function called with the token. |
| data-error-callback | — | Name of a global function called with the error. |
Programmatic API
import { render, reset } from '@tillstack/tillgate'
// or, with the script tag, use the global `TillGate`
const id = TillGate.render('#gate', {
sitekey: 'tg_site_xxxxxxxxxxxx',
theme: 'dark',
callback(token) {
// token is also written to the form's hidden `tillgate-response` field
console.log('verified', token)
},
errorCallback(err) {
console.warn('tillgate failed', err)
},
})
// Start over (e.g. after a failed form submit):
TillGate.reset(id)render(target, options?) accepts a CSS selector or an element and returns a
widget id. Options override the matching data-* attributes.
Modes
The mode is configured per-site in the dashboard and returned by the server; the widget adapts:
interactive— shows a checkbox the visitor clicks to start.managed/invisible— starts automatically on render.
How the check works
GET /api/tillgate/challenge?sitekey=…→{ challenge, difficulty, ttl, mode }.- In a Web Worker, find a decimal
solution("0","1", …) such thatSHA256("${challenge}.${solution}")has at leastdifficultyleading zero bits. A compact synchronous SHA-256 is used (WebCrypto's async digest is far too slow to loop); it self-verifies against theSHA256("abc")vector before doing any work. POST /api/tillgate/solvewith{ challenge, solution, hostname }→{ token, ttl }.- The token is written to the hidden
tillgate-responsefield, passed to yourcallback, and refreshed before it expires.
Accessibility & motion
The status text is announced via aria-live, the checkbox is keyboard-operable,
and all animation is disabled under prefers-reduced-motion.
License
MIT © Tillafrica
